Wayfair · Data Analytics
E-commerce Sales Analysis
The work
- In [1]:
import pandas as pd import matplotlib.pyplot as plt - In [2]:
orders = pd.read_csv( "orders.csv", parse_dates=["order_date"] ) order_items = pd.read_csv("order_items.csv") products = pd.read_csv("products.csv") customers = pd.read_csv("customers.csv") - In [3]:
print("Orders:", orders.shape) print("Order Items:", order_items.shape) print("Products:", products.shape) print("Customers:", customers.shape)Orders: (9245, 7) Order Items: (15608, 5) Products: (90, 5) Customers: (3600, 4)
- In [4]:
orders.head()order_id customer_id order_date status payment_method \ 0 100001 1530 01-01-2024 09:24 completed credit_card 1 100002 3531 01-01-2024 10:37 completed credit_card 2 100003 840 01-01-2024 10:59 completed credit_card 3 100004 725 01-01-2024 12:48 completed apple_pay 4 100005 3452 01-01-2024 17:44 completed gift_card discount_code shipping_fee 0 NaN 6.95 1 NaN 0.00 2 NaN 0.00 3 WELCOME10 0.00 4 NaN 0.00
- In [5]:
orders.info()<class 'pandas.core.frame.DataFrame'> RangeIndex: 9245 entries, 0 to 9244 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 order_id 9245 non-null int64 1 customer_id 9245 non-null int64 2 order_date 9245 non-null object 3 status 9245 non-null object 4 payment_method 9245 non-null object 5 discount_code 1411 non-null object 6 shipping_fee 9245 non-null float64 dtypes: float64(1), int64(2), object(4) memory usage: 505.7+ KB
- In [6]:
# 1. Check date data type print(orders["order_date"].dtype)object
- In [7]:
# 2. Check missing values in all columns print(orders.isnull().sum())order_id 0 customer_id 0 order_date 0 status 0 payment_method 0 discount_code 7834 shipping_fee 0 dtype: int64
- In [8]:
# 3. Inspect status labels and discount code values print(orders["status"].value_counts(dropna=False)) print(orders["discount_code"].value_counts(dropna=False).head(10))status completed 8490 returned 409 canceled 346 Name: count, dtype: int64 discount_code NaN 7834 HOLIDAY20 782 WELCOME10 391 SPRING15 238 Name: count, dtype: int64
- In [9]:
# Preserve original values for validation original_dates = orders["order_date"].copy() # Convert to datetime orders["order_date"] = pd.to_datetime( orders["order_date"], errors="coerce" ) print("Data type:", orders["order_date"].dtype) print("Invalid dates:", orders["order_date"].isna().sum())Data type: datetime64[ns] Invalid dates: 5686
- In [10]:
invalid_dates = orders.loc[ orders["order_date"].isna(), ["order_id"] ].copy() invalid_dates["original_date"] = original_dates.loc[ orders["order_date"].isna() ] invalid_dates.head(20)order_id original_date 116 100117 13-01-2024 06:39 117 100118 13-01-2024 09:36 118 100119 13-01-2024 10:27 119 100120 13-01-2024 18:14 120 100121 13-01-2024 18:34 121 100122 13-01-2024 21:36 122 100123 14-01-2024 06:15 123 100124 14-01-2024 10:12 124 100125 14-01-2024 11:11 125 100126 14-01-2024 14:11 126 100127 14-01-2024 14:13 127 100128 14-01-2024 14:55 128 100129 14-01-2024 18:01 129 100130 14-01-2024 19:13 131 100131 14-01-2024 19:31 132 100132 14-01-2024 21:30 133 100133 14-01-2024 23:36 134 100134 14-01-2024 23:41 135 100135 15-01-2024 08:47 136 100136 15-01-2024 09:49
- In [11]:
orders["order_date"] = pd.to_datetime( original_dates, format="mixed", dayfirst=True, errors="coerce" ) print("Data type:", orders["order_date"].dtype) print("Invalid dates:", orders["order_date"].isna().sum())Data type: datetime64[ns] Invalid dates: 0
- In [12]:
orders[["order_id", "order_date"]].head(10)order_id order_date 0 100001 2024-01-01 09:24:00 1 100002 2024-01-01 10:37:00 2 100003 2024-01-01 10:59:00 3 100004 2024-01-01 12:48:00 4 100005 2024-01-01 17:44:00 5 100006 2024-01-01 19:10:00 6 100007 2024-01-01 23:25:00 7 100008 2024-01-02 06:08:00 8 100009 2024-01-02 10:16:00 9 100010 2024-01-02 15:18:00
- In [13]:
orders.loc[ orders["order_date"].isna(), ["order_id"] ].head()Empty DataFrame Columns: [order_id] Index: []
- In [14]:
print( "Duplicate order IDs:", orders["order_id"].duplicated().sum() ) print( "Exact duplicate rows:", orders.duplicated().sum() )Duplicate order IDs: 38 Exact duplicate rows: 38
- In [15]:
duplicate_orders = orders[ orders["order_id"].duplicated(keep=False) ].sort_values("order_id") duplicate_ordersorder_id customer_id order_date status payment_method \ 47 100048 3320 2024-01-06 16:37:00 completed credit_card 8560 100048 3320 2024-01-06 16:37:00 completed credit_card 7556 100303 853 2024-02-03 08:33:00 completed apple_pay 303 100303 853 2024-02-03 08:33:00 completed apple_pay 5488 100710 526 2024-03-13 13:39:00 completed credit_card ... ... ... ... ... ... 8617 108585 1995 2025-11-28 12:43:00 completed apple_pay 9104 109068 1981 2025-12-20 13:27:00 completed credit_card 3104 109068 1981 2025-12-20 13:27:00 completed credit_card 4213 109087 3358 2025-12-22 08:35:00 completed credit_card 9124 109087 3358 2025-12-22 08:35:00 completed credit_card discount_code shipping_fee 47 NaN 6.95 8560 NaN 6.95 7556 NaN 0.00 303 NaN 0.00 5488 NaN 0.00 ... ... ... 8617 HOLIDAY20 0.00 9104 HOLIDAY20 6.95 3104 HOLIDAY20 6.95 4213 HOLIDAY20 0.00 9124 HOLIDAY20 0.00 [76 rows x 7 columns] - In [16]:
duplicate_orders.groupby("order_id").nunique()customer_id order_date status payment_method discount_code \ order_id 100048 1 1 1 1 0 100303 1 1 1 1 0 100710 1 1 1 1 0 100779 1 1 1 1 0 101380 1 1 1 1 0 101813 1 1 1 1 0 102000 1 1 1 1 0 102064 1 1 1 1 0 102345 1 1 1 1 0 102517 1 1 1 1 0 103209 1 1 1 1 1 103267 1 1 1 1 0 103807 1 1 1 1 0 103810 1 1 1 1 1 104239 1 1 1 1 0 104259 1 1 1 1 1 104399 1 1 1 1 0 104455 1 1 1 1 0 104607 1 1 1 1 0 104695 1 1 1 1 0 104855 1 1 1 1 0 105006 1 1 1 1 0 105094 1 1 1 1 0 105481 1 1 1 1 0 106199 1
- In [17]:
orders_clean = orders.drop_duplicates().copy() print("Original rows:", len(orders)) print("Cleaned rows:", len(orders_clean)) print( "Duplicate order IDs remaining:", orders_clean["order_id"].duplicated().sum() )Original rows: 9245 Cleaned rows: 9207 Duplicate order IDs remaining: 0
Duplicate order records: The orders dataset contained 38 exact duplicate rows. Since the dataset is defined as one row per order, one copy of each duplicated record was retained. The original dataset was preserved for traceability.
- In [18]:
print(products["category"].value_counts(dropna=False))category Kitchen 10 Outdoor 9 Furniture 9 Bath 9 Lighting 8 Bedding 8 Decor 8 Storage 7 decor 4 Bedding 2 Kitchen 2 Storage 2 bedding 1 KITCHEN 1 Kitchen 1 kitchen 1 FURNITURE 1 furniture 1 Furniture 1 bath 1 BEDDING 1 Outdoor 1 LIGHTING 1 OUTDOOR 1 Name: count, dtype: int64
- In [19]:
print( products["category"] .dropna() .unique() )['Kitchen' ' Kitchen' 'Kitchen ' 'kitchen' 'KITCHEN' ' Bedding' 'Bedding' 'bedding' 'BEDDING' 'Bath' 'bath' 'Furniture' 'Furniture ' 'furniture' 'FURNITURE' 'Lighting' 'LIGHTING' 'Outdoor' 'OUTDOOR' ' Outdoor' 'Decor' 'decor' 'Storage' 'Storage ']
- In [20]:
products_clean = products.copy() products_clean["category"] = ( products_clean["category"] .str.strip() .str.title() ) - In [21]:
print(products_clean["category"].value_counts()) print("Unique categories:", products_clean["category"].nunique())category Kitchen 15 Bedding 12 Furniture 12 Decor 12 Outdoor 11 Bath 10 Lighting 9 Storage 9 Name: count, dtype: int64 Unique categories: 8
Category standardization: Product category labels contained inconsistent capitalization and leading/trailing whitespace. Whitespace was removed and capitalization was standardized to title case. The original category column was preserved in the source DataFrame.
- In [22]:
items_with_date = order_items.merge( orders_clean[["order_id", "order_date"]], on="order_id", how="left", validate="many_to_one" ) missing_prices = items_with_date[ items_with_date["unit_price"].isna() ] missing_prices[ ["order_id", "product_id", "order_date", "quantity"] ].head(10)order_id product_id order_date quantity 55 100033 1086 2024-01-05 10:24:00 1 110 100062 1020 2024-01-07 13:33:00 1 197 100116 1087 2024-01-12 23:20:00 2 366 100210 1014 2024-01-24 11:11:00 1 626 100361 1077 2024-02-08 15:58:00 1 685 100396 1015 2024-02-12 07:12:00 1 822 100468 1004 2024-02-18 17:21:00 2 958 100545 1090 2024-02-26 10:33:00 2 1007 100573 1090 2024-02-28 21:06:00 1 1125 100639 1087 2024-03-05 18:11:00 2
- In [23]:
# Historical prices available for each product known_prices = items_with_date[ items_with_date["unit_price"].notna() ] historical_summary = ( known_prices.groupby("product_id")["unit_price"] .agg(["count", "min", "max", "nunique"]) ) historical_summary.head(10)count min max nunique product_id 1001 219 61.99 65.09 2 1002 201 138.99 145.94 2 1003 212 22.99 24.14 2 1004 191 75.99 79.79 2 1005 207 79.99 83.99 2 1006 226 16.99 17.84 2 1007 211 71.99 75.59 2 1008 215 43.99 46.19 2 1009 221 131.99 138.59 2 1010 221 96.99 101.84 2
- In [24]:
missing_product_ids = missing_prices["product_id"].unique() historical_summary.loc[ historical_summary.index.isin(missing_product_ids) ].sort_values("nunique", ascending=False)count min max nunique product_id 1001 219 61.99 65.09 2 1002 201 138.99 145.94 2 1003 212 22.99 24.14 2 1004 191 75.99 79.79 2 1005 207 79.99 83.99 2 ... ... ... ... ... 1086 205 97.99 102.89 2 1087 226 45.99 48.29 2 1088 192 20.99 22.04 2 1089 209 53.99 56.69 2 1090 211 17.99 18.89 2 [78 rows x 4 columns]
- In [25]:
known_prices = ( items_with_date.loc[ items_with_date["unit_price"].notna(), ["product_id", "order_date", "unit_price"] ] .rename(columns={ "order_date": "reference_date", "unit_price": "historical_unit_price" }) .sort_values("reference_date") ) candidates = pd.merge_asof( missing_prices.sort_values("order_date"), known_prices, left_on="order_date", right_on="reference_date", by="product_id", direction="nearest" ) - In [26]:
candidates["date_gap_days"] = ( candidates["order_date"] - candidates["reference_date"] ).abs().dt.total_seconds() / 86400 candidates[ [ "order_id", "product_id", "order_date", "reference_date", "historical_unit_price", "date_gap_days" ] ].head(15)order_id product_id order_date reference_date \ 0 100033 1086 2024-01-05 10:24:00 2024-01-05 09:51:00 1 100062 1020 2024-01-07 13:33:00 2024-01-10 22:58:00 2 100116 1087 2024-01-12 23:20:00 2024-01-12 20:43:00 3 100210 1014 2024-01-24 11:11:00 2024-01-23 18:15:00 4 100361 1077 2024-02-08 15:58:00 2024-02-11 09:48:00 5 100396 1015 2024-02-12 07:12:00 2024-02-13 17:38:00 6 100468 1004 2024-02-18 17:21:00 2024-02-24 08:15:00 7 100545 1090 2024-02-26 10:33:00 2024-02-26 21:48:00 8 100573 1090 2024-02-28 21:06:00 2024-02-27 09:37:00 9 100639 1087 2024-03-05 18:11:00 2024-03-06 06:19:00 10 100664 1037 2024-03-08 11:25:00 2024-03-08 07:54:00 11 100834 1041 2024-03-25 17:37:00 2024-03-27 11:51:00 12 100869 1085 2024-03-28 09:30:00 2024-03-30 15:41:00 13 100891 1002 2024-03-30 14:43:00 2024-03-22 10:58:00 14 100968 1069 2024-04-07 11:57:00 2024-04-08 18:44:00 historical_unit_price date_gap_days 0 97.99 0.022917 1 54.99 3.392361 2 45.99 0.109028 3 36.99 0.705556 4 25.99 2.743056 5 96.99 1.434722 6 75.99 5.620833 7 17.99 0.468750 8 17.99 1.478472 9 45.99 0.505556 10 28.99 0.146528 11 553.99 1.759722 12 115.99 2.257639 13 138.99 8.156250 14 26.99 1.282639 - In [27]:
candidates["date_gap_days"].describe()count 199.000000 mean 2.068223 std 2.481226 min 0.002083 25% 0.460069 50% 1.359722 75% 2.354167 max 15.096528 Name: date_gap_days, dtype: float64
- In [28]:
print( "Within 7 days:", (candidates["date_gap_days"] <= 7).sum() ) print( "More than 7 days:", (candidates["date_gap_days"] > 7).sum() )Within 7 days: 188 More than 7 days: 11
- In [29]:
candidates_previous = pd.merge_asof( missing_prices.sort_values("order_date"), known_prices.sort_values("reference_date"), left_on="order_date", right_on="reference_date", by="product_id", direction="backward" ) - In [30]:
candidates_previous = pd.merge_asof( missing_prices.sort_values("order_date"), known_prices.sort_values("reference_date"), left_on="order_date", right_on="reference_date", by="product_id", direction="backward" ) candidates_previous["date_gap_days"] = ( candidates_previous["order_date"] - candidates_previous["reference_date"] ).dt.total_seconds() / 86400 print( "No previous price found:", candidates_previous["reference_date"].isna().sum() ) print( "Within 7 days:", (candidates_previous["date_gap_days"] <= 7).sum() ) print( "More than 7 days:", (candidates_previous["date_gap_days"] > 7).sum() )No previous price found: 1 Within 7 days: 162 More than 7 days: 36
- In [31]:
candidates_previous["imputation_eligible"] = ( candidates_previous["reference_date"].notna() & (candidates_previous["date_gap_days"] <= 7) ) print( candidates_previous["imputation_eligible"].value_counts() )imputation_eligible True 162 False 37 Name: count, dtype: int64
- In [32]:
candidates_previous.loc[ candidates_previous["imputation_eligible"], [ "order_id", "product_id", "order_date", "reference_date", "historical_unit_price", "date_gap_days" ] ].head(10)order_id product_id order_date reference_date \ 0 100033 1086 2024-01-05 10:24:00 2024-01-05 09:51:00 2 100116 1087 2024-01-12 23:20:00 2024-01-12 20:43:00 3 100210 1014 2024-01-24 11:11:00 2024-01-23 18:15:00 4 100361 1077 2024-02-08 15:58:00 2024-02-03 12:49:00 5 100396 1015 2024-02-12 07:12:00 2024-02-09 14:10:00 7 100545 1090 2024-02-26 10:33:00 2024-02-25 08:07:00 8 100573 1090 2024-02-28 21:06:00 2024-02-27 09:37:00 9 100639 1087 2024-03-05 18:11:00 2024-03-03 10:42:00 10 100664 1037 2024-03-08 11:25:00 2024-03-08 07:54:00 11 100834 1041 2024-03-25 17:37:00 2024-03-22 13:58:00 historical_unit_price date_gap_days 0 97.99 0.022917 2 45.99 0.109028 3 36.99 0.705556 4 25.99 5.131250 5 96.99 2.709722 7 17.99 1.101389 8 17.99 1.478472 9 45.99 2.311806 10 28.99 0.146528 11 553.99 3.152083 - In [33]:
items_clean = items_with_date.copy() # Keep track of which prices were originally missing items_clean["price_was_missing"] = ( items_clean["unit_price"].isna() ) - In [34]:
price_lookup = candidates_previous[ [ "order_id", "product_id", "historical_unit_price", "date_gap_days", "imputation_eligible" ] ].copy() items_clean = items_clean.merge( price_lookup, on=["order_id", "product_id"], how="left", validate="many_to_one" ) - In [35]:
eligible = ( items_clean["price_was_missing"] & items_clean["imputation_eligible"].fillna(False) ) items_clean.loc[eligible, "unit_price"] = ( items_clean.loc[eligible, "historical_unit_price"] )C:\Users\MAYURESH GAIKWAD\AppData\Local\Temp\ipykernel_30840\192088112.py:3: FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)` & items_clean["imputation_eligible"].fillna(False) - In [36]:
print("Original missing prices:", items_clean["price_was_missing"].sum()) print("Prices imputed:", (eligible).sum()) print("Prices still missing:", items_clean["unit_price"].isna().sum())Original missing prices: 199 Prices imputed: 162 Prices still missing: 37
- In [37]:
print(products_clean["category"].value_counts()) print( "Total unique categories:", products_clean["category"].nunique() )category Kitchen 15 Bedding 12 Furniture 12 Decor 12 Outdoor 11 Bath 10 Lighting 9 Storage 9 Name: count, dtype: int64 Total unique categories: 8
- In [38]:
items_clean["net_line_sales"] = ( items_clean["quantity"] * items_clean["unit_price"] - items_clean["discount_amount"] ) - In [39]:
print( "Rows with missing prices:", items_clean["unit_price"].isna().sum() ) print( "Rows with missing line sales:", items_clean["net_line_sales"].isna().sum() )Rows with missing prices: 37 Rows with missing line sales: 37
- In [40]:
sales_data = items_clean.merge( orders_clean[ ["order_id", "order_date", "status", "shipping_fee"] ], on="order_id", how="left", validate="many_to_one" ) - In [41]:
completed_sales = sales_data[ sales_data["status"] == "completed" ].copy() print("Completed order-item rows:", len(completed_sales)) print( "Completed sales with missing prices:", completed_sales["unit_price"].isna().sum() )Completed order-item rows: 14283 Completed sales with missing prices: 33
- In [42]:
print( "Missing-price rows:", completed_sales["unit_price"].isna().sum() ) print( "Units affected:", completed_sales.loc[ completed_sales["unit_price"].isna(), "quantity" ].sum() )Missing-price rows: 33 Units affected: 40
- In [43]:
known_sales = completed_sales[ completed_sales["unit_price"].notna() ] unknown_sales = completed_sales[ completed_sales["unit_price"].isna() ] print( "Known net product sales:", known_sales["net_line_sales"].sum() ) print( "Rows with unknown net sales:", len(unknown_sales) )Known net product sales: 1876682.2799999998 Rows with unknown net sales: 33
- In [44]:
price_coverage = ( completed_sales["unit_price"].notna().mean() * 100 ) print(f"Price coverage: {price_coverage:.2f}%")Price coverage: 99.77%
- In [45]:
print(completed_sales.columns.tolist())['order_id', 'product_id', 'quantity', 'unit_price', 'discount_amount', 'order_date_x', 'price_was_missing', 'historical_unit_price', 'date_gap_days', 'imputation_eligible', 'net_line_sales', 'order_date_y', 'status', 'shipping_fee']
- In [46]:
monthly_revenue = ( completed_sales .groupby( completed_sales["order_date_x"].dt.to_period("M") )["net_line_sales"] .sum(min_count=1) ) monthly_revenueorder_date_x 2024-01 54334.52 2024-02 63122.64 2024-03 71531.80 2024-04 64747.75 2024-05 73145.85 2024-06 67108.39 2024-07 76739.07 2024-08 62625.59 2024-09 60054.02 2024-10 67129.85 2024-11 100382.26 2024-12 86517.00 2025-01 72895.31 2025-02 70306.83 2025-03 78616.92 2025-04 79926.95 2025-05 82817.63 2025-06 92022.44 2025-07 83255.06 2025-08 91547.36 2025-09 75215.77 2025-10 81938.59 2025-11 129661.14 2025-12 91039.54 Freq: M, Name: net_line_sales, dtype: float64
- In [47]:
monthly_df = monthly_revenue.rename("revenue").reset_index() monthly_df["year"] = monthly_df["order_date_x"].dt.year monthly_df["month"] = monthly_df["order_date_x"].dt.month comparison = monthly_df.pivot( index="month", columns="year", values="revenue" ) comparison["YoY Growth %"] = ( (comparison[2025] - comparison[2024]) / comparison[2024] ) * 100 comparison.round(2)year 2024 2025 YoY Growth % month 1 54334.52 72895.31 34.16 2 63122.64 70306.83 11.38 3 71531.80 78616.92 9.90 4 64747.75 79926.95 23.44 5 73145.85 82817.63 13.22 6 67108.39 92022.44 37.13 7 76739.07 83255.06 8.49 8 62625.59 91547.36 46.18 9 60054.02 75215.77 25.25 10 67129.85 81938.59 22.06 11 100382.26 129661.14 29.17 12 86517.00 91039.54 5.23
- In [48]:
annual_revenue = monthly_df.groupby("year")["revenue"].sum() print("Annual Revenue:") print(annual_revenue.round(2)) growth_pct = ( (annual_revenue.loc[2025] - annual_revenue.loc[2024]) / annual_revenue.loc[2024] ) * 100 print(f"\nAnnual Revenue Growth: {growth_pct:.2f}%")Annual Revenue: year 2024 847438.74 2025 1029243.54 Name: revenue, dtype: float64 Annual Revenue Growth: 21.45%
- In [49]:
plt.figure(figsize=(12, 5)) for year in [2024, 2025]: year_data = monthly_df[monthly_df["year"] == year] plt.plot( year_data["month"], year_data["revenue"], marker="o", label=str(year) ) plt.title("Wayfair Monthly Net Product Sales: 2024 vs 2025") plt.xlabel("Month") plt.ylabel("Net Product Sales (USD)") plt.xticks(range(1, 13)) plt.legend(title="Year") plt.grid(alpha=0.3) plt.tight_layout() plt.show()<Figure size 1200x500 with 1 Axes>
- In [50]:
import calendar plt.figure(figsize=(12, 5)) for year in [2024, 2025]: year_data = monthly_df[monthly_df["year"] == year] plt.plot( year_data["month"], year_data["revenue"], marker="o", label=str(year) ) plt.title("Monthly Net Product Sales: 2024 vs 2025") plt.xlabel("Month") plt.ylabel("Net Product Sales (USD)") plt.xticks( range(1, 13), [calendar.month_abbr[m] for m in range(1, 13)] ) plt.legend(title="Year") plt.grid(alpha=0.3) plt.tight_layout() plt.show()<Figure size 1200x500 with 1 Axes>
- In [51]:
category_sales = completed_sales.merge( products_clean[["product_id", "product_name", "category"]], on="product_id", how="left", validate="many_to_one" ) category_revenue = ( category_sales .groupby("category")["net_line_sales"] .sum() .sort_values(ascending=False) ) category_revenuecategory Furniture 320305.87 Kitchen 279258.87 Decor 277680.43 Bedding 266698.36 Outdoor 246902.87 Lighting 237400.26 Storage 139469.01 Bath 108966.61 Name: net_line_sales, dtype: float64
- In [52]:
category_summary = category_revenue.reset_index() category_summary.columns = ["category", "revenue"] category_summary["revenue_share_pct"] = ( category_summary["revenue"] / category_summary["revenue"].sum() ) * 100 category_summarycategory revenue revenue_share_pct 0 Furniture 320305.87 17.067666 1 Kitchen 279258.87 14.880455 2 Decor 277680.43 14.796347 3 Bedding 266698.36 14.211162 4 Outdoor 246902.87 13.156349 5 Lighting 237400.26 12.649997 6 Storage 139469.01 7.431679 7 Bath 108966.61 5.806343
- In [53]:
category_summary = category_summary.sort_values( "revenue", ascending=True ) plt.figure(figsize=(10, 6)) plt.barh( category_summary["category"], category_summary["revenue"] ) plt.title("Net Product Sales by Category") plt.xlabel("Net Product Sales (USD)") plt.ylabel("Product Category") plt.tight_layout() plt.show()<Figure size 1000x600 with 1 Axes>
- In [54]:
top_products = ( category_sales .groupby(["product_id", "product_name", "category"])["net_line_sales"] .sum() .sort_values(ascending=False) .head(10) ) top_productsproduct_id product_name category 1059 Cotton Patio Chair Outdoor 67034.90 1042 Classic Dining Chair Pair Furniture 51243.95 1072 Coastal Picture Frame Set Decor 44000.98 1018 Ceramic Quilt Bedding 41400.47 1044 Bamboo Nightstand Furniture 41365.73 1076 Modern Wreath Decor 41022.95 1041 Nordic Side Table Furniture 40394.19 1054 Velvet String Lights Lighting 38838.42 1080 Classic Garland Decor 38681.43 1040 Marble Bookshelf Furniture 38595.66 Name: net_line_sales, dtype: float64
- In [55]:
category_summary = category_revenue.reset_index() category_summary.columns = ["category", "revenue"] category_summary["revenue_share_pct"] = ( category_summary["revenue"] / category_summary["revenue"].sum() ) * 100 category_summary.sort_values( "revenue", ascending=False ).round(2)category revenue revenue_share_pct 0 Furniture 320305.87 17.07 1 Kitchen 279258.87 14.88 2 Decor 277680.43 14.80 3 Bedding 266698.36 14.21 4 Outdoor 246902.87 13.16 5 Lighting 237400.26 12.65 6 Storage 139469.01 7.43 7 Bath 108966.61 5.81
- In [56]:
order_categories = ( order_items[["order_id", "product_id"]] .drop_duplicates() .merge( products_clean[["product_id", "category"]], on="product_id", how="left", validate="many_to_one" ) .merge( orders_clean[["order_id", "status"]], on="order_id", how="left", validate="many_to_one" ) ) - In [57]:
category_orders = ( order_categories .groupby("category") .agg( total_orders=("order_id", "nunique"), returned_orders=( "status", lambda s: order_categories.loc[ s.index, "order_id" ][s == "returned"].nunique() ), canceled_orders=( "status", lambda s: order_categories.loc[ s.index, "order_id" ][s == "canceled"].nunique() ) ) ) category_orders["return_rate_pct"] = ( category_orders["returned_orders"] / category_orders["total_orders"] * 100 ) category_orders["cancellation_rate_pct"] = ( category_orders["canceled_orders"] / category_orders["total_orders"] * 100 ) category_orders.sort_values( "return_rate_pct", ascending=False ).round(2)total_orders returned_orders canceled_orders return_rate_pct \ category Furniture 1007 118 31 11.72 Bedding 1800 91 59 5.06 Bath 1643 73 75 4.44 Outdoor 1353 60 50 4.43 Decor 2729 114 95 4.18 Storage 1733 72 58 4.15 Kitchen 2844 118 108 4.15 Lighting 1227 49 36 3.99 cancellation_rate_pct category Furniture 3.08 Bedding 3.28 Bath 4.56 Outdoor 3.70 Decor 3.48 Storage 3.35 Kitchen 3.80 Lighting 2.93 - In [58]:
category_orders.sort_values( "return_rate_pct", ascending=False ).round(2)total_orders returned_orders canceled_orders return_rate_pct \ category Furniture 1007 118 31 11.72 Bedding 1800 91 59 5.06 Bath 1643 73 75 4.44 Outdoor 1353 60 50 4.43 Decor 2729 114 95 4.18 Storage 1733 72 58 4.15 Kitchen 2844 118 108 4.15 Lighting 1227 49 36 3.99 cancellation_rate_pct category Furniture 3.08 Bedding 3.28 Bath 4.56 Outdoor 3.70 Decor 3.48 Storage 3.35 Kitchen 3.80 Lighting 2.93 - In [59]:
category_aov = ( category_sales .groupby("category") .agg( category_revenue=("net_line_sales", "sum"), completed_orders=("order_id", "nunique") ) ) category_aov["category_aov"] = ( category_aov["category_revenue"] / category_aov["completed_orders"] ) category_aov.sort_values( "category_aov", ascending=False ).round(2)category_revenue completed_orders category_aov category Furniture 320305.87 858 373.32 Lighting 237400.26 1142 207.88 Outdoor 246902.87 1243 198.63 Bedding 266698.36 1650 161.64 Decor 277680.43 2520 110.19 Kitchen 279258.87 2618 106.67 Storage 139469.01 1603 87.00 Bath 108966.61 1495 72.89
- In [60]:
return_chart = category_orders.sort_values( "return_rate_pct", ascending=True ) plt.figure(figsize=(10, 6)) plt.barh( return_chart.index, return_chart["return_rate_pct"] ) plt.title("Order Return Rate by Product Category") plt.xlabel("Return Rate (%)") plt.ylabel("Category") plt.tight_layout() plt.show()<Figure size 1000x600 with 1 Axes>
- In [61]:
category_sales["month"] = ( category_sales["order_date_x"].dt.to_period("M") ) monthly_category_revenue = ( category_sales .groupby(["month", "category"])["net_line_sales"] .sum() .reset_index() ) monthly_category_revenue.head(10)month category net_line_sales 0 2024-01 Bath 3954.39 1 2024-01 Bedding 10777.03 2 2024-01 Decor 8205.65 3 2024-01 Furniture 6623.77 4 2024-01 Kitchen 8872.35 5 2024-01 Lighting 9052.85 6 2024-01 Outdoor 1674.89 7 2024-01 Storage 5173.59 8 2024-02 Bath 4133.90 9 2024-02 Bedding 9993.80
- In [62]:
monthly_category_revenue["monthly_total"] = ( monthly_category_revenue .groupby("month")["net_line_sales"] .transform("sum") ) monthly_category_revenue["revenue_share_pct"] = ( monthly_category_revenue["net_line_sales"] / monthly_category_revenue["monthly_total"] ) * 100 monthly_category_revenue.head(10)month category net_line_sales monthly_total revenue_share_pct 0 2024-01 Bath 3954.39 54334.52 7.277859 1 2024-01 Bedding 10777.03 54334.52 19.834591 2 2024-01 Decor 8205.65 54334.52 15.102093 3 2024-01 Furniture 6623.77 54334.52 12.190721 4 2024-01 Kitchen 8872.35 54334.52 16.329122 5 2024-01 Lighting 9052.85 54334.52 16.661323 6 2024-01 Outdoor 1674.89 54334.52 3.082552 7 2024-01 Storage 5173.59 54334.52 9.521737 8 2024-02 Bath 4133.90 63122.64 6.548997 9 2024-02 Bedding 9993.80 63122.64 15.832354
- In [63]:
category_pivot = monthly_category_revenue.pivot( index="month", columns="category", values="revenue_share_pct" ) category_pivot.plot( kind="bar", stacked=True, figsize=(14, 6) ) plt.title("Monthly Revenue Mix by Product Category") plt.xlabel("Month") plt.ylabel("Share of Monthly Net Product Sales (%)") plt.legend(title="Category", bbox_to_anchor=(1.02, 1)) plt.tight_layout() plt.show()<Figure size 1400x600 with 1 Axes>
- In [64]:
mix = monthly_category_revenue.copy() mix["year"] = mix["month"].dt.year mix["month_num"] = mix["month"].dt.month share_comparison = mix.pivot( index=["month_num", "category"], columns="year", values="revenue_share_pct" ) share_comparison["change_pp"] = ( share_comparison[2025] - share_comparison[2024] ) share_comparison["abs_change"] = ( share_comparison["change_pp"].abs() ) share_comparison.sort_values( "abs_change", ascending=False ).head(10).round(2)year 2024 2025 change_pp abs_change month_num category 12 Furniture 23.74 13.86 -9.88 9.88 1 Furniture 12.19 20.88 8.69 8.69 6 Outdoor 34.30 26.36 -7.94 7.94 Furniture 5.88 13.58 7.70 7.70 2 Lighting 14.74 8.67 -6.07 6.07 9 Lighting 12.00 17.67 5.67 5.67 11 Furniture 15.53 20.33 4.80 4.80 2 Furniture 16.70 21.25 4.56 4.56 7 Lighting 9.49 13.76 4.27 4.27 1 Bedding 19.83 15.65 -4.18 4.18 - In [65]:
december_furniture = monthly_category_revenue[ (monthly_category_revenue["month"].dt.month == 12) & (monthly_category_revenue["category"] == "Furniture") ].copy() december_furniture[ ["month", "net_line_sales", "monthly_total", "revenue_share_pct"] ]month net_line_sales monthly_total revenue_share_pct 91 2024-12 20539.09 86517.00 23.739947 187 2025-12 12613.63 91039.54 13.855112
- In [66]:
dec_furniture_products = category_sales[ (category_sales["category"] == "Furniture") & (category_sales["order_date_x"].dt.month == 12) ].copy() dec_furniture_products["year"] = ( dec_furniture_products["order_date_x"].dt.year ) product_comparison = dec_furniture_products.pivot_table( index=["product_id", "product_name"], columns="year", values="net_line_sales", aggfunc="sum", fill_value=0 ) product_comparison["change_usd"] = ( product_comparison[2025] - product_comparison[2024] ) product_comparison.sort_values( "change_usd" ).head(10).round(2)year 2024 2025 change_usd product_id product_name 1042 Classic Dining Chair Pair 6811.11 2275.53 -4535.58 1041 Nordic Side Table 3545.53 0.00 -3545.53 1045 Rattan Console Table 2027.96 1064.68 -963.28 1038 Linen Accent Chair 816.37 0.00 -816.37 1039 Nordic Coffee Table 1451.97 914.74 -537.23 1043 Stoneware Bar Stool 409.56 241.90 -167.66 1046 Nordic Storage Bench 571.96 450.42 -121.54 1048 Linen Loveseat 531.96 411.57 -120.39 1049 Linen TV Stand 457.16 520.03 62.87 1040 Marble Bookshelf 3720.52 3806.38 85.86
- In [67]:
dec_furniture_products["year"] = ( dec_furniture_products["order_date_x"].dt.year ) product_demand = dec_furniture_products.pivot_table( index=["product_id", "product_name"], columns="year", values=["order_id", "quantity"], aggfunc={ "order_id": "nunique", "quantity": "sum" }, fill_value=0 ) product_demandorder_id quantity year 2024 2025 2024 2025 product_id product_name 1038 Linen Accent Chair 3 0 3 0 1039 Nordic Coffee Table 3 2 3 2 1040 Marble Bookshelf 8 8 8 8 1041 Nordic Side Table 7 0 7 0 1042 Classic Dining Chair Pair 9 3 9 3 1043 Stoneware Bar Stool 4 2 4 2 1044 Bamboo Nightstand 0 5 0 5 1045 Rattan Console Table 4 2 4 2 1046 Nordic Storage Bench 4 3 4 3 1047 Modern Writing Desk 1 2 1 2 1048 Linen Loveseat 4 3 4 3 1049 Linen TV Stand 4 4 4 4
- In [68]:
dec_furniture_products["discount_rate"] = ( dec_furniture_products["discount_amount"] / ( dec_furniture_products["quantity"] * dec_furniture_products["unit_price"] ) ).where( dec_furniture_products["quantity"] * dec_furniture_products["unit_price"] > 0 ) discount_comparison = dec_furniture_products.pivot_table( index=["product_id", "product_name"], columns="year", values="discount_rate", aggfunc="mean" ) discount_comparison.round(3)year 2024 2025 product_id product_name 1038 Linen Accent Chair 0.133 NaN 1039 Nordic Coffee Table 0.000 0.100 1040 Marble Bookshelf 0.025 0.050 1041 Nordic Side Table 0.086 NaN 1042 Classic Dining Chair Pair 0.022 0.067 1043 Stoneware Bar Stool 0.200 0.100 1044 Bamboo Nightstand NaN 0.080 1045 Rattan Console Table 0.000 0.000 1046 Nordic Storage Bench 0.000 0.000 1047 Modern Writing Desk 0.000 0.100 1048 Linen Loveseat 0.050 0.067 1049 Linen TV Stand 0.100 0.025
- In [69]:
furniture_monthly = ( category_sales[ category_sales["category"] == "Furniture" ] .assign( year=lambda df: df["order_date_x"].dt.year, month_num=lambda df: df["order_date_x"].dt.month ) .pivot_table( index="month_num", columns="year", values="net_line_sales", aggfunc="sum" ) ) furniture_monthly["change_pct"] = ( (furniture_monthly[2025] - furniture_monthly[2024]) / furniture_monthly[2024] ) * 100 furniture_monthly.round(2)year 2024 2025 change_pct month_num 1 6623.77 15218.95 129.76 2 10540.29 14942.81 41.77 3 15726.34 16154.11 2.72 4 12135.58 12162.22 0.22 5 12018.51 12703.93 5.70 6 3947.86 12497.81 216.57 7 11626.26 13421.79 15.44 8 9676.72 13358.49 38.05 9 9747.72 13887.98 42.47 10 13935.50 14879.82 6.78 11 15585.74 26360.95 69.14 12 20539.09 12613.63 -38.59
- In [70]:
annual_revenue = monthly_revenue.groupby( monthly_revenue.index.year ).sum() print(annual_revenue.round(2)) annual_growth = ( (annual_revenue.loc[2025] - annual_revenue.loc[2024]) / annual_revenue.loc[2024] ) * 100 print(f"Annual growth: {annual_growth:.2f}%")order_date_x 2024 847438.74 2025 1029243.54 Name: net_line_sales, dtype: float64 Annual growth: 21.45%
- In [71]:
furniture_orders = ( order_categories[ order_categories["category"] == "Furniture" ] .drop_duplicates(["order_id", "category"]) ) product_returns = ( order_items[["order_id", "product_id"]] .drop_duplicates() .merge( products_clean[["product_id", "product_name", "category"]], on="product_id", how="left" ) .merge( orders_clean[["order_id", "status"]], on="order_id", how="left" ) ) product_returns = product_returns[ product_returns["category"] == "Furniture" ] return_summary = ( product_returns .groupby(["product_id", "product_name"]) .agg( total_orders=("order_id", "nunique"), returned_orders=( "status", lambda s: (s == "returned").sum() ) ) ) return_summary["return_rate_pct"] = ( return_summary["returned_orders"] / return_summary["total_orders"] * 100 ) return_summary.sort_values( "return_rate_pct", ascending=False ).round(2).head(10)total_orders returned_orders \ product_id product_name 1043 Stoneware Bar Stool 81 16 1048 Linen Loveseat 100 14 1046 Nordic Storage Bench 89 12 1047 Modern Writing Desk 97 13 1049 Linen TV Stand 93 12 1038 Linen Accent Chair 81 10 1041 Nordic Side Table 85 10 1040 Marble Bookshelf 94 9 1042 Classic Dining Chair Pair 79 7 1039 Nordic Coffee Table 84 7 return_rate_pct product_id product_name 1043 Stoneware Bar Stool 19.75 1048 Linen Loveseat 14.00 1046 Nordic Storage Bench 13.48 1047 Modern Writing Desk 13.40 1049 Linen TV Stand 12.90 1038 Linen Accent Chair 12.35 1041 Nordic Side Table 11.76 1040 Marble Bookshelf 9.57 1042 Classic Dining Chair Pair 8.86 1039 Nordic Coffee Table 8.33
Wayfair E-commerce Sales Analysis
Two-Year Sales Performance Review | 2024–2025
A Python-based analysis of e-commerce orders to evaluate sales growth, category performance, product-level return risks, and opportunities for the next planning cycle.
Table of Contents
- Project Overview
- Business Questions
- Dataset
- Tools and Methods
- Data Quality and Cleaning
- Metric Definitions
- Key Findings
- Visual Analysis
- Business Recommendations
- Limitations
- Repository Structure
- Reproducibility
Project Overview
Wayfair's e-commerce team needs a clear review of online sales performance in 2024 and 2025 to inform the next annual plan. The supplied data includes order records, order-item lines, product attributes, and customer information. Before analysis, the data was profiled for missing values, duplicate records, date-format issues, and inconsistent product-category labels.
This project uses Python and Pandas to prepare the data, calculate net product sales, compare monthly performance, evaluate category and product contribution, and investigate return and cancellation patterns. The analysis focuses on turning the results into practical, evidence-led recommendations.
Business Questions
- How did net product sales change between 2024 and 2025?
- Which months had the highest sales, and what patterns appear across the two years?
- Which product categories and products contributed the most sales?
- Which categories had the highest associated-order return and cancellation rates?
- How did category sales mix change from month to month?
- Which issues should be investigated before next year's planning decisions?
Dataset
The analysis uses four CSV files covering orders placed between January 2024 and December 2025.
| File | Grain | Relevant fields |
|---|---|---|
orders.csv | One row per order | Order date, status, customer, payment method, discount code, shipping fee |
order_items.csv | One row per product line | Product, quantity, unit price, line discount |
products.csv | One row per product | Product name, category, list price, unit cost |
customers.csv | One row per customer | Signup date, state, acquisition channel |
The supplied files contained 9,245 order rows, 15,608 order-item rows, 90 products, and 3,600 customer records before cleaning.
Tools and Methods
- Python, Pandas, NumPy, and Matplotlib
- Data profiling and missing-value checks
- Datetime parsing and validation
- Exact-duplicate detection
- Product-category label normalization
- Historical-price imputation using a documented date-gap rule
- DataFrame joins, grouping, aggregation, and pivot tables
- Monthly trend and year-over-year analysis
- Category revenue share, associated-order return and cancellation rates, and category sales-per-order analysis
- Business interpretation and recommendation development
Data Quality and Cleaning
Order dates
The source contained day-first date strings, including values such as 13-01-2024 06:39. Dates were parsed into Pandas datetime values using day-first interpretation, and invalid parsed values were inspected rather than automatically deleted.
Duplicate order records
The orders table contained 38 exact duplicate rows. Since the table is defined as one row per order, one copy of each exact duplicate was retained.
| Check | Rows |
|---|---|
| Original order rows | 9,245 |
| Exact duplicate rows removed | 38 |
| Rows after exact deduplication | 9,207 |
Deduplication was based on exact row equality, not merely on repeated order_id values.
Product-category labels
Category values contained inconsistent capitalization and whitespace. These labels were trimmed and standardized, consolidating the product catalog into eight categories: Bath, Bedding, Decor, Furniture, Kitchen, Lighting, Outdoor, and Storage.
Missing unit prices
There were 199 missing unit_price values in the order-item data. Historical prices for the same product were investigated. A price from the nearest available transaction was used as an estimate when the reference transaction was within seven days of the order date.
| Price handling | Rows |
|---|---|
| Originally missing unit prices | 199 |
| Prices imputed under the seven-day rule | 162 |
| Prices left missing | 37 |
The seven-day cutoff is an analytical assumption, not a verified pricing rule. Imputed prices are estimates and may not match the original transaction price, particularly around promotions or price changes. The 37 unresolved values were left missing rather than replaced with zero.
Missing discount codes
Missing discount codes were retained as missing during initial inspection. They were not automatically interpreted as proof that no promotion was applied.
Metric Definitions
Net product sales
The line-level calculation is:
Net Line Sales = Quantity × Unit Price − Discount Amount
The primary sales trend includes orders whose final status is completed. Orders marked canceled or returned are excluded from this completed-order sales measure. Shipping fees are excluded.
The figures reported here are calculated net product sales, not profit or exact refund-adjusted revenue. Rows with unresolved unit prices do not contribute a known line-sales value to the sum, so reported sales may be understated. Historical-price imputation introduces additional uncertainty.
Year-over-year growth
YoY Growth (%) = (Current-Year Sales − Prior-Year Sales) / Prior-Year Sales × 100
Monthly comparisons match each calendar month in 2025 with the same month in 2024.
Category revenue share
Category Revenue Share (%) = Category Net Product Sales / Total Net Product Sales × 100
Revenue share describes the composition of calculated sales. A category's share can change even when its absolute sales move in a different direction.
Return and cancellation rates
An order is associated with a category when at least one item in that order belongs to that category. For each category, the associated-order return or cancellation rate is the number of unique associated orders with that status divided by the number of unique orders associated with that category.
An order containing products from multiple categories may be counted in more than one category. These rates are category-associated order rates; they do not establish which item caused a return.
Category sales per associated completed order
Category Sales per Associated Completed Order = Category Net Product Sales / Unique Completed Orders Associated with the Category
Because an order may contain products from multiple categories, category order counts can overlap. This metric is not a mutually exclusive allocation of the full basket value to a single category.
Key Findings
1. Calculated net product sales grew by 21.45%
| Metric | Result |
|---|---|
| 2024 net product sales | $847,438.74 |
| 2025 net product sales | $1,029,243.54 |
| Absolute change | +$181,804.80 |
| Year-over-year growth | +21.45% |
Every corresponding month in 2025 had higher calculated sales than the same month in 2024. November was the strongest month in both years, at approximately $100.4K in 2024 and $129.7K in 2025.
This repeated peak is worth investigating for planning, but two years of observations are not enough to establish a stable long-term seasonal pattern.
2. Furniture led category sales and had the highest associated-order return rate
Furniture generated approximately $320,305.87 in calculated net product sales, representing 17.07% of category sales in the analysis. Its category-associated return rate was 11.72%, the highest among the eight categories.
At product level, Stoneware Bar Stool had a 19.75% associated-order return rate, with 16 returned orders among 81 associated orders. Other Furniture products with elevated rates included Linen Loveseat, Nordic Storage Bench, Modern Writing Desk, Linen TV Stand, and Linen Accent Chair.
These results identify products for investigation; they do not establish the reasons for returns or quantify the financial cost of those returns.
3. December Furniture sales declined while total December sales increased
Furniture's calculated net product sales fell from $20,539.09 in December 2024 to $12,613.63 in December 2025, a decline of approximately 38.59%. Overall calculated December net product sales rose from $86,517.00 to $91,039.54, an increase of approximately 5.23%.
The product-level analysis showed lower December 2025 sales for products including Classic Dining Chair Pair and Nordic Side Table. Availability, listing status, demand, and promotion history are useful areas to investigate, but the available data does not establish the cause of the decline.
Visual Analysis
Monthly net product sales: 2024 vs. 2025
The line chart compares corresponding months and highlights the November sales peak in both years.
Net product sales by category
Furniture generated the highest calculated category sales, followed by Kitchen and Decor.
Return rate by category
Furniture had the highest category-associated order return rate in the analysis.
Monthly category mix
The stacked chart shows how each category's share of calculated monthly net product sales changed over the two-year period.
Business Recommendations
1. Investigate Furniture returns
Review product-level return rates and returned-order volumes, then examine actual return reasons. Prioritize products with both elevated rates and meaningful return volumes. Depending on the evidence, corrective actions could include supplier quality checks, packaging improvements, or clearer product dimensions and descriptions.
Measures to monitor: product- and category-associated return rates, returned sales value, and contribution margin after returns.
2. Investigate the December performance of selected Furniture products
Audit inventory availability, website listing status, product-page traffic, conversion, and promotion history for Linen Accent Chair and Nordic Side Table. Compare December 2025 with December 2024 before deciding whether to adjust availability, merchandising, or promotions.
Measures to monitor: orders, units sold, conversion rate, and net product sales.
Limitations
- The sales figures are provisional because 162 prices were imputed and 37 prices remained missing.
- Historical-price imputation may not reproduce the original transaction price.
- Order status does not provide actual refund amounts or return reasons.
- Category-associated order rates can overlap when an order contains items from multiple categories.
- Product-associated return rates do not prove that a specific product caused an order to be returned.
- Inventory, stock-out history, website traffic, conversion, and promotion-calendar data were not supplied.
- Profit, contribution margin, and exact refund-adjusted revenue cannot be established from the available fields and analysis.
- Two years of observations are insufficient to confirm long-term seasonality.
Repository Structure
wayfair-ecommerce-sales-analysis/
├── README.md
├── wayfair_sales_analysis.ipynb
├── data/
│ ├── orders.csv
│ ├── order_items.csv
│ ├── products.csv
│ └── customers.csv
└── images/
├── monthly-revenue-trend.png
├── category-revenue.png
├── category-return-rate.png
└── monthly-category-mix.pngReproducibility
Install the required packages:
pip install pandas numpy matplotlib jupyterPlace the four CSV files in the data/ directory, open wayfair_sales_analysis.ipynb in Jupyter Notebook or VS Code, and run the notebook from top to bottom. If the notebook uses different file paths or filenames, update this section to match the repository.
Conclusion
The analysis indicates that calculated net product sales increased in 2025, while Furniture combined the largest category sales contribution with the highest associated-order return rate. The decline in December sales for selected Furniture products also warrants further investigation.
The next planning cycle should prioritize understanding Furniture returns and validating the drivers behind the selected products' December performance. The remaining missing prices and the effect of imputation should be reviewed before the sales estimates are treated as final.
The brief this answers
Clean two years of online store orders and turn them into a clear sales review for next year's plan.
Try it yourselfOther answers to this brief
The same problem, solved by somebody else.
Wayfair · Data AnalyticsE-commerce Sales Analysis
Clean two years of online store orders and turn them into a clear sales review for next year's plan.
- Data cleaning
- pandas
- Time series trends
- easy
Sachin Singh Negi0
Wayfair · Data AnalyticsE-commerce Sales Analysis
Clean two years of online store orders and turn them into a clear sales review for next year's plan.
- Data cleaning
- pandas
- Time series trends
- easy
Harshita Soni0
Wayfair · Data AnalyticsE-commerce Sales Analysis
Clean two years of online store orders and turn them into a clear sales review for next year's plan.
- Data cleaning
- pandas
- Time series trends
- easy
V I N A Y J A G T A P0
Discussion
Nothing here yet. Ask how they approached it, or tell them what you would have done differently.