diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..f56f150 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,19 +19,24 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + return(np.mean(arr)) def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + return(arr*2) def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + mask = arr > 25 + return( arr[mask]) + # ============================================================ @@ -42,7 +47,12 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 - pass + DATA = 'datasets/ecommerce/products.csv' + prices = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=3) + + mask1 = prices > 1000 + result = mask1.sum() + return result def yellow_top3_stock_indices(stocks): @@ -51,7 +61,11 @@ def yellow_top3_stock_indices(stocks): 提示:np.argsort """ # TODO: 你的程式碼 - pass + DATA = 'datasets/ecommerce/products.csv' + stocks = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=4) + + return np.argsort(stocks)[-3:] + def yellow_restock_cost(prices, stocks): @@ -60,7 +74,13 @@ def yellow_restock_cost(prices, stocks): 提示:布林遮罩 + .sum() """ # TODO: 你的程式碼 - pass + DATA = 'datasets/ecommerce/products.csv' + prices = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=3) + stocks = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=4) + low_prices = prices < 500 + return(sum(prices[low_prices])*50) + + # ============================================================ @@ -77,4 +97,9 @@ def red_double11_prices(prices, stocks): 提示:np.where 可以巢狀使用 """ # TODO: 你的程式碼 - pass + DATA = 'datasets/ecommerce/products.csv' + prices = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=3) + stocks = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=4) + + result1 = np.where( stocks>=100,prices*0.7,np.where((stocks>19) &(stocks<100),prices*0.9,prices)) + return(result1) diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..96af6a5 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -20,7 +20,8 @@ def green_read_csv(): 提示:pd.read_csv() """ # TODO: 你的程式碼 - pass + DataFrame =pd.read_csv("datasets/ecommerce/orders_raw.csv") + return DataFrame def green_shape(df): @@ -29,7 +30,8 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 - pass + df =pd.read_csv("datasets/ecommerce/orders_raw.csv") + return df.shape def green_dtypes(df): @@ -38,7 +40,8 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 - pass + df =pd.read_csv("datasets/ecommerce/orders_raw.csv") + return df.dtypes # ============================================================ @@ -52,7 +55,10 @@ def yellow_clean_columns(df): 提示:df.columns.str.strip().str.lower() """ # TODO: 你的程式碼 - pass + df =pd.read_csv("datasets/ecommerce/orders_raw.csv") + df.columns = df.columns.str.strip().str.lower() + return df + def yellow_clean_amount(df): @@ -63,7 +69,18 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass + df =pd.read_csv("datasets/ecommerce/orders_raw.csv") + df['amount'] = ( + df['amount'] + .astype(str) + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float) + ) + return df + + + def yellow_drop_duplicates(df): @@ -72,7 +89,11 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass + df =pd.read_csv("datasets/ecommerce/orders_raw.csv") + df = df.drop_duplicates() + return df + + # ============================================================ @@ -93,4 +114,29 @@ def red_clean_orders(path): 提示:pd.to_datetime(errors='coerce') """ # TODO: 你的程式碼 - pass + df =pd.read_csv("datasets/ecommerce/orders_raw.csv") + df.columns = df.columns.str.strip().str.lower() + df['amount'] = ( + df['amount'] + .astype(str) + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float) + ) + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + df = df.dropna(subset=['order_date']) + df = df.dropna(subset=['amount']) + df = df.drop_duplicates() + return df + + + + + + + + + + + + diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..3348a98 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -24,19 +24,49 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - pass + DATA = 'datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + return df + + + def green_row_count(df): """回傳 DataFrame 的列數 (int)""" # TODO: 你的程式碼 - pass + DATA = 'datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + return int(df.shape[0]) def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" # TODO: 你的程式碼 - pass + DATA = 'datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + return list(df.columns) # ============================================================ @@ -50,7 +80,19 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - pass + DATA = 'datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + high_category = df.groupby('category')['amount'].sum() + + return(high_category.index[0]) + def yellow_gold_vip_stats(df): @@ -60,7 +102,20 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - pass + DATA = 'datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + vip = df[df['vip_level'] == 'Gold'] + viporder = int(vip.shape[0]) + vipamount =vip['amount'].sum() + + return viporder,vipamount def yellow_region_avg_amount(df): @@ -70,7 +125,17 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + DATA = 'datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + regionamount = df.groupby('region')['amount'].mean() + return regionamount # ============================================================ @@ -94,4 +159,16 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + multi = df.groupby("customer_id").agg( + customer_name=('customer_name','first'), + R=('order_date', 'max' ), + F=('order_id', 'count'), + M=('amount', 'sum') + ).reset_index() + multi_sorted = multi.sort_values('M', ascending=False).head() + + return multi_sorted + + + + diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..eabc8f9 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -27,7 +27,15 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + + df['month'] = df['order_date'].dt.month + ts = df.set_index('order_date').sort_index() + monthly = ts['amount'].resample('ME').mean() + monthly.index = monthly.index.month + + return monthly def green_top3_dates(): @@ -37,7 +45,12 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + + mostorderdate = df['order_date'].value_counts().head(3) + + return mostorderdate def green_date_range(): @@ -46,7 +59,11 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + Timestamp = df['order_date'].min() ,df['order_date'].max() + + return Timestamp # ============================================================ @@ -60,7 +77,14 @@ def yellow_monthly_revenue(): 提示:set_index('order_date').resample('ME')['amount'].sum() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + ts = df.set_index('order_date').sort_index() + monthly_revenue = ts['amount'].resample('ME').sum() + + return monthly_revenue + + def yellow_rolling_avg(monthly_revenue): @@ -71,7 +95,17 @@ def yellow_rolling_avg(monthly_revenue): 提示:.rolling(window=3).mean() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + ts = df.set_index('order_date').sort_index() + monthly_revenue = ts['amount'].resample('ME').sum() + ma3 = monthly_revenue.rolling(window=3).mean() + + + return ma3 + + + def yellow_category_median(df): @@ -81,7 +115,11 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + categorymidian =df.groupby('category')['amount'].median().sort_values(ascending=False) + + return categorymidian # ============================================================ @@ -101,4 +139,26 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + df['year'] = df['order_date'].dt.year + df['month'] = df['order_date'].dt.month + df['weekday'] = df['order_date'].dt.day_name() + df['year_mon'] = df['order_date'].dt.to_period('M') + + Monthly_Report =df.groupby('month').agg( + order_count = ('order_id','count'), + revenue =('amount','sum'), + active_customers = ('customer_name','nunique'), +) + Monthly_Report['avg_order_value'] = Monthly_Report ['revenue']/ Monthly_Report['order_count'] + Monthly_Report['revenue_growth']= Monthly_Report ['revenue'].pct_change() + + + return(Monthly_Report) + + + + + + diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..0799fe3 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -29,7 +29,18 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + + category_counts = df.groupby('category')['order_id'].count().reset_index() + + fig, ax = plt.subplots() + + sns.barplot(data=category_counts, x='category', y='order_id', ax=ax, color='green') + + ax.set_title('Order Count by Category') + + return fig def green_hist_amount(): @@ -39,7 +50,12 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + fig, ax = plt.subplots() + sns.histplot(data=df['amount'],bins=20,ax=ax) + + return fig def green_set_labels(): @@ -51,7 +67,18 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + + category_counts = df.groupby('category')['order_id'].count().reset_index() + + fig, ax = plt.subplots() + + sns.barplot(data=category_counts, x='category', y='order_id', ax=ax, color='green') + + ax.set_title('Order Count by Category') + + return fig # ============================================================ @@ -68,7 +95,21 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + df['month'] = df['order_date'].dt.month + monthly_North = df.query("region == 'North'").groupby('month')['amount'].sum().reset_index() + monthly_South = df.query("region == 'South'").groupby('month')['amount'].sum().reset_index() + + fig = plt.figure(figsize=(10, 4)) + sns.lineplot(data=monthly_North, x='month', y='amount', marker='o', linewidth=2) + sns.lineplot(data=monthly_South, x='month', y='amount', marker='s', linewidth=2) + plt.title('Line Region Trend', fontsize=14, fontweight='bold') + plt.xlabel('Month') + plt.ylabel('Revenue (NT$)') + plt.ylabel('Revenue (NT$)') + + return fig def yellow_box_vip(): @@ -78,8 +119,19 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 - pass - + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + + fig = plt.figure(figsize=(9, 4)) + sns.boxplot(data=df, x='vip_level', y='amount', palette='Set2', hue='category', legend=False) + plt.title('box_vip', fontweight='bold') + plt.xlabel('vip_level') + plt.ylabel('amount') + plt.xticks(rotation=15) + plt.tight_layout() + plt.show() + + return fig def yellow_scatter_price_amount(): """ @@ -88,7 +140,20 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + + fig = plt.figure(figsize=(9, 5)) + sns.scatterplot(data=df, x='unit_price', y='amount', + hue='category', alpha=0.6, s=60) + plt.title('scatter price amount', fontweight='bold') + plt.xlabel('Unit Price') + plt.ylabel('Amount') + plt.legend(bbox_to_anchor=(1.02, 1), loc='upper left') + plt.tight_layout() + plt.show() + + return fig # ============================================================ @@ -107,4 +172,45 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + df['month'] = df['order_date'].dt.month + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + + monthly_Electronics = df.query("category == 'Electronics'").groupby('month')['amount'].sum().reset_index() + + sns.lineplot(data=monthly_Electronics, x='month', y='amount', marker='o', linewidth=2,color='brown', ax=axes[0, 0]) + plt.title('category dashboard', fontsize=14, fontweight='bold') + plt.xlabel('Month') + plt.ylabel('Revenue (NT$)') + + + monthly_Electronics1 = df.query("category == 'Electronics'").groupby('region')['amount'].sum().sort_values(ascending=False).reset_index() + + sns.barplot(data=monthly_Electronics1, x='region', y='amount', palette='viridis', hue='region', legend=False, ax=axes[0, 1]) + plt.title('Revenue by Electronics', fontweight='bold') + plt.xlabel('Region') + plt.ylabel('Revenue (NT$)') + + + monthly_Electronics2 = df.query("category == 'Electronics'").groupby('product_name')['amount'].sum().sort_values(ascending=False).reset_index().head() + + sns.barplot(data=monthly_Electronics2, x='amount', y='product_name', color= 'green',ax=axes[1, 0]) + plt.title('Revenue by Electronics', fontweight='bold') + plt.xlabel('Region') + plt.ylabel('Revenue (NT$)') + + + monthly_Electronics3 = df.query("category == 'Electronics'").groupby('product_name')['amount'].sum().sort_values(ascending=False).reset_index() + + sns.histplot(data=monthly_Electronics3,x='amount',y='product_name',bins=20, color='pink', ax=axes[1, 1]) + + plt.tight_layout() + + + return fig + + + + + diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..658ef43 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -26,7 +26,15 @@ def green_plotly_bar(): 提示:px.bar() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + plotly_bar = df.groupby('category', as_index=False)['amount'].sum().sort_values('amount', ascending=False) + fig = px.bar(plotly_bar, x='category', y='amount', text='amount',color='category', title='Plotly Bar') + fig.update_traces(texttemplate='%{text:,.0f}', textposition='outside') + fig.update_layout(height=480, showlegend=False) + + return fig + def green_plotly_line(): @@ -37,8 +45,16 @@ def green_plotly_line(): 提示:先 groupby 月份算總營收,再 px.line() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + df['month'] = df['order_date'].dt.month + df['month'] = df['order_date'].dt.month + monthly1 = df.groupby('month')['amount'].sum().reset_index() + fig = px.line(monthly1, x='month', y='amount', markers=True, + title='plotly line') + fig.update_layout(height=400) + return fig def green_plotly_pie(): """ @@ -48,8 +64,14 @@ def green_plotly_pie(): 提示:px.pie() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + vip_rev = df.groupby('vip_level', as_index=False)['amount'].sum() + fig = px.pie(vip_rev, names='vip_level', values='amount', + title='VIP Level Share', hole=0.4) + fig.update_layout(height=400) + return fig # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) @@ -63,7 +85,32 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 回傳:合併後的 DataFrame """ # TODO: 你的程式碼 - pass + def load_and_clean_orders(path): + df = pd.read_csv(path) + df.columns = df.columns.str.strip().str.lower() + df['amount'] = ( + df['amount'].astype(str) + .str.replace('$','', regex=False) + .str.replace(',','', regex=False) + .astype(float) + ) + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + df = df.dropna(subset=['order_date']) + df['qty'] = df['qty'].fillna(df['qty'].median()) + df = df.drop_duplicates() + + return df + + orders = load_and_clean_orders('datasets/ecommerce/orders_raw.csv') + customers = pd.read_csv('datasets/ecommerce/customers.csv') + products = pd.read_csv('datasets/ecommerce/products.csv') + full = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + + return full def yellow_kpi_summary(df): @@ -77,7 +124,20 @@ def yellow_kpi_summary(df): } """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + total_revenue = df['amount'].sum() + order_count = df['order_id'].count() + active_customers =df['customer_name'].nunique() + avg_order_value = total_revenue / order_count + + kpi_summary = { + "total_revenue": total_revenue , + "order_count": order_count, + "active_customers": active_customers, + "avg_order_value": avg_order_value , + } + return kpi_summary def yellow_plotly_scatter(df): @@ -91,8 +151,12 @@ def yellow_plotly_scatter(df): 提示:px.scatter(hover_data=['product_name']) """ # TODO: 你的程式碼 - pass + fig = px.scatter(df, x='unit_price', y='amount', + color='category', hover_data=['product_name', 'customer_name'], + title='Unit Price vs Order Amount') + fig.update_layout(height=450) + return fig # ============================================================ # 🔴 挑戰題(25 分) @@ -115,4 +179,72 @@ def red_dashboard(): 提示:from plotly.subplots import make_subplots """ # TODO: 你的程式碼 - pass + def load_and_clean_orders(path): + df = pd.read_csv(path) + df.columns = df.columns.str.strip().str.lower() + df['amount'] = ( + df['amount'].astype(str) + .str.replace('$','', regex=False) + .str.replace(',','', regex=False) + .astype(float) + ) + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + df = df.dropna(subset=['order_date']) + df['qty'] = df['qty'].fillna(df['qty'].median()) + df = df.drop_duplicates() + + return df + + orders = load_and_clean_orders('datasets/ecommerce/orders_raw.csv') + customers = pd.read_csv('datasets/ecommerce/customers.csv') + products = pd.read_csv('datasets/ecommerce/products.csv') + enriched = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + enriched['month'] = enriched['order_date'].dt.month + + monthly = enriched.groupby('month', as_index=False)['amount'].sum() + top_prod = (enriched.groupby('product_name', as_index=False)['amount'] + .sum().sort_values('amount', ascending=False).head(10)) + region_rev = enriched.groupby('region', as_index=False)['amount'].sum() + cat_rev = enriched.groupby('category', as_index=False) ['amount'].sum() + +# 2x2 subplot + fig = make_subplots( + rows=2, cols=2, + subplot_titles=('Monthly Revenue Trend', + 'Top 10 Products', + 'Revenue by Region', + 'Category Share'), + specs=[[{'type':'xy'}, {'type':'xy'}], + [{'type':'xy'}, {'type':'domain'}]], + ) + + fig.add_trace(go.Scatter(x=monthly['month'], y=monthly['amount'], + mode='lines+markers', name='Monthly'), row=1, col=1) + fig.add_trace(go.Bar(x=top_prod['product_name'], y=top_prod['amount'], + name='Top Products'), row=1, col=2) + fig.add_trace(go.Bar(x=region_rev['region'], y=region_rev['amount'], + name='Region'), row=2, col=1) + fig.add_trace(go.Pie(labels=cat_rev['category'], values=cat_rev['amount'], + name='Category', hole=0.4), row=2, col=2) + + fig.update_layout( + title_text='E-Commerce Sales Dashboard — 2025', + height=750, showlegend=False, + ) + fig.update_xaxes(tickangle=45, row=1, col=2) + + + return fig + + + + + + + + +