BANA409BANA409

Assignment 4: Data App, Linear Regression, SGD and Random Forest

This assignment has two pieces:

  • Part 1: finish the Data App In-Class Group Exercise: a Streamlit dashboard for a real decision-maker, deployed, with your design decisions written up.
  • Part 2: price used phones for a trade-in platform. Compare a baseline, linear regression, SGD, and a random forest, interpret them, and find out how much money each one makes or loses.

Part 1: The Data App

Finish all five steps of the In-Class Group Exercise. You worked through it as a team, but everyone submits their own app:

  • The dataset can be shared. You can use your team's dataset, or choose a different Kaggle dataset you like better.
  • The repository and the app must be in your own accounts. The GitHub repository must be under your own GitHub account, and the app must be deployed from that repository with your own Streamlit Cloud account. A link to a teammate's repository or app does not count, even if you helped build it.
  • The README.md must be your own writing, even if your teammates made similar choices.

If you finished in class and the repository and app are already in your own accounts, check your README.md against Steps 1 to 4 and hand in the same app.

As the exercise says, you are graded mostly on your decisions and justifications in the README.md: who the dashboard is for and what decision it supports, what you showed and what you cut and why, and how often the data should be refreshed. A small app with well-argued choices beats a crowded one.

Part 2: Phone Trade-in Pricing

You are a data analyst at a phone trade-in platform, like Apple Trade In. A seller enters their used phone's details on the website, the platform makes an instant offer, and if the seller accepts, the platform buys the phone and resells it. The platform needs two things from you:

  1. a model that predicts what a used phone will resell for, and
  2. a decision: how much of that predicted value to offer the seller.

Offer too little and sellers go elsewhere. Offer too much and the platform loses money. You will build the model, then use it to make the decision, and find out how much money each model makes or loses.

You will build one preprocessing pipeline, train three models on it (linear regression, linear regression trained with SGD, and a random forest), compare them with a model-free baseline, and turn their predictions into dollars. Work in a new notebook called phone-trade-in.ipynb.

Answer every question in a markdown cell, right after the code that produced the numbers it asks about. Answers anywhere else are not graded. Two rules hold for all of Part 2, as in Assignment 3:

  1. The test set is locked. Tune with cross validation on the training set, and score each model on the test set exactly once, at the end. See How to Use the Test Set.
  2. Back every answer with numbers. Don't just write "model A is better"; say which score shows it and give the values, for example "model A's test RMSE is $40 lower than model B's".

The Data

Download phone_trade_in.csv.zip and unzip it into the folder where you run your notebook. Each of its 5,000 rows is one used phone the platform bought and resold, quoted on October 1, 2025:

ColumnMeaning
phone_idan identifier for the row
brandApple, Samsung, Huawei, Xiaomi, OPPO, vivo, or Honor
tierflagship, mid-range, or budget
release_yearthe year the phone model came out (2019 to 2025)
days_since_launchhow many days the phone model had been on the market when it was quoted
launch_pricewhat this phone cost new, with its storage, in US dollars
storage_gbstorage in GB
battery_healththe battery's remaining capacity in %, where 100 means as good as new; missing when the phone did not report it
screen_conditionperfect, scratched, or cracked
has_boxwhether the seller still has the original box
repaired_beforewhether the phone has been repaired before
resale_pricethe target: what the platform resold the phone for, in US dollars

The data is simulated: the patterns in it are realistic, but they were chosen for this assignment rather than collected from a real platform. So treat its findings, such as which brand holds its value best, as findings about this data, not about the real phone market.

Part 2A: Prepare the Data

Read phone_trade_in.csv and explore it. The features are every column except phone_id and resale_price, and the target is resale_price. Split the data into a training set and a test set with train_test_split(X, y, test_size=0.2, random_state=409). Use exactly these settings, so that everyone's test set holds the same 1,000 phones.

Then build a preprocessor that handles the missing values and prepares the numerical and categorical features, as in Full Pipeline. You will attach every model in Part 2B to this same preprocessor.

The Questions

  1. Missing battery health. Which phones are missing battery_health? Compare the share of missing values for each brand in the training set. Then check what battery health depends on: compare its median by brand and by phone age (2025 − release_year). If you fill every missing value with the median of all phones, which phones get a guess that is too high, and which too low? Why might this matter less for the models than you'd expect? (Hint: what else in the data tells the model how old a phone is?)
  2. Price over time. Plot resale_price against days_since_launch for the training set as a scatter plot (see Matplotlib). Describe the pattern in two or three sentences. Could a straight line describe it?

Part 2B: Train and Compare the Models

Attach each model to the same preprocessor in its own pipeline. For each model, calculate three scores on the test set: RMSE, MAE, and R-squared (mean_squared_error, mean_absolute_error, and r2_score from sklearn.metrics). For grid search, use cv=5 and scoring='neg_root_mean_squared_error', so that the search picks the parameters with the lowest RMSE.

Step 1. A model-free baseline. Predict the training set's average resale price for every phone in the test set, and score it. See The RMSE and a Baseline.

Step 2. Linear regression. Attach LinearRegression() to the preprocessor in a pipeline called lr_pipeline, with the steps named 'preprocessor' and 'lin_reg' (Part 2C uses these names). Train it on the training set, and score it. You do not need grid search: linear regression has no parameters to tune, and its best weights are calculated exactly.

Step 3. SGD. Attach SGDRegressor(random_state=409) to the preprocessor and tune it with GridSearchCV, trying:

  • max_iter, the maximum number of passes over the data: 10000 and 100000
  • tol, the stopping tolerance: 1e-5 and 1e-3
  • eta0, the starting learning rate: 0.01 and 0.1

Print the best parameters, then score the best model. See SGD via Sklearn for how the pipeline step names go into the parameter grid.

Step 4. Random forest. Attach RandomForestRegressor(random_state=409) to the preprocessor. It is the random forest from Ensemble Learning, predicting a number instead of a class. Tune it with GridSearchCV, trying:

  • n_estimators, the number of trees: 100 and 150
  • max_depth: 5 and 10

Print the best parameters, then score the best model.

Step 5. The comparison. Put the scores of all four models (the baseline, linear regression, SGD, and the random forest) into one table, with a row for each model and a column for each score.

The Questions

  1. Which model is best? Which model wins on all three scores? By how much does each model beat the baseline?
  2. Same model, two algorithms. Linear regression and SGD fit the same kind of model (see Model vs. Training Algorithm). Are their scores close? Why would you expect them to be?
  3. Why the forest wins. Count how many test phones linear regression predicts a negative price for. Use that count and your plot from Question 2 to explain why the random forest does so much better on this data.

Part 2C: Interpret the Models

Step 1. The linear weights. Print the linear regression's weights, one for each column the preprocessor produces:

weights = pd.Series(lr_pipeline['lin_reg'].coef_,
                    index=lr_pipeline['preprocessor'].get_feature_names_out())
print(weights.round(0))

Each numerical weight is the change in the predicted price, in dollars, when that feature goes up by one unit, or by one standard deviation if you standardized it. Each category gets its own weight. Compare two categories of the same column by subtracting their weights: for example, cat__screen_condition_cracked minus cat__screen_condition_perfect is how much the model says a cracked screen costs compared with a perfect one.

Step 2. Permutation importance. Use permutation importance on the test set, as in Feature Importance. Run it for lr_pipeline and for the best random forest, with scoring='neg_root_mean_squared_error', n_repeats=10, and random_state=409, and show each model's five most important features.

The Questions

  1. What the linear model says. According to the linear weights, how much does a cracked screen cost compared with a perfect one? How much more is an Apple phone worth than an otherwise identical Honor? What does having been repaired before cost?
  2. One number for every phone. The linear model charges the same amount for a cracked screen on a $150 budget phone and on a $900 flagship. Is that believable? What would a random forest be able to do differently?
  3. What drives the price? Which features matter most to each model in the permutation importance? Do the two models agree?

Part 2D: The Trade-in Decision

Now turn the predictions into money. The platform offers a seller a fixed share of the model's predicted price. We use this simple model of how sellers and the platform behave:

  • The offer is offer_rate × predicted price.
  • The seller accepts only if the offer is at least 75% of what the phone is really worth (its resale_price); otherwise they sell elsewhere.
  • The platform's profit on an accepted phone is resale_price − offer, which is negative when the platform paid more than the phone is worth.

Use this function to calculate how many phones a model's predictions buy, and the total profit, on the test set:

def trade_in_profit(predicted, actual, offer_rate):
    """Offer offer_rate x predicted price; a seller accepts if the offer is at least
    75% of what the phone is really worth; the platform resells at that worth."""
    offer = offer_rate * predicted
    accepted = offer >= 0.75 * actual
    profit = (actual - offer)[accepted]
    return accepted.sum(), profit.sum()

For example, trade_in_profit(y_pred_lr, y_test.to_numpy(), 0.75) returns the number of phones bought and the total profit when linear regression's predictions are offered at 75%.

Step 1. For the baseline, linear regression, and the random forest, calculate the number of phones bought and the total profit at each offer rate from 0.60 to 0.90 in steps of 0.05. Show the results in one table, with a row for each offer rate.

Step 2. For linear regression at an offer rate of 0.75, find where the money is lost: calculate the profit for each phone age (2025 − release_year), counting 0 for phones that were not bought.

The Questions

  1. The best offer. For the random forest, which offer rate makes the most money, and how much? Explain the trade-off: what goes wrong when the offer rate is too low, and when it is too high?
  2. The baseline. The baseline loses money at every offer rate. Which phones end up accepting its offers, and why does that guarantee a loss? (Hint: the baseline offers the same price for every phone.)
  3. R-squared is not profit. Linear regression's R-squared is respectable, yet it loses money at every offer rate. Using your table from Step 2, explain where it loses money and why.

Part 2E: Advise the Platform

  1. Write to the platform manager. In four to six sentences, recommend which model to use and what offer rate to set, how much profit to expect on 1,000 phones, and what the main risks are. Write it for a manager who does not know machine learning: no "RMSE", no "R-squared", no "random forest". Back it with dollar numbers from your results.

Submission

Upload phone-trade-in.ipynb to Canvas. It carries both parts:

  1. Part 1: put your deployed app URL and your GitHub repository URL, both from your own accounts, in the first markdown cell of the notebook. The repository must contain the README.md from Step 5 of the exercise.
  2. Part 2: the rest of the notebook. Run it from top to bottom before you upload it, so that every output shown was produced by the code above it.

On this page