diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..b7b603b 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,19 +19,22 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + return float(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]) + return (arr[arr > 25]) # ============================================================ @@ -42,7 +45,8 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 - pass + mask = prices > 1000 + return mask.sum() def yellow_top3_stock_indices(stocks): @@ -51,7 +55,9 @@ def yellow_top3_stock_indices(stocks): 提示:np.argsort """ # TODO: 你的程式碼 - pass + stocks_3 = np.argsort(stocks) + return (stocks_3[::-1][:3]) + def yellow_restock_cost(prices, stocks): @@ -60,7 +66,8 @@ def yellow_restock_cost(prices, stocks): 提示:布林遮罩 + .sum() """ # TODO: 你的程式碼 - pass + prices_500 = prices[prices < 500]*50 + return (prices_500.sum()) # ============================================================ @@ -77,4 +84,16 @@ def red_double11_prices(prices, stocks): 提示:np.where 可以巢狀使用 """ # TODO: 你的程式碼 - pass + prices = np.array(prices) + stocks = np.array(stocks) + + new_prices = np.where( + stocks >= 100, + prices * 0.7, + np.where( + stocks >= 20, + prices * 0.9, + prices + ) +) + return new_prices \ No newline at end of file diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..8e8a5df 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -20,7 +20,7 @@ def green_read_csv(): 提示:pd.read_csv() """ # TODO: 你的程式碼 - pass + return pd.read_csv('datasets/ecommerce/orders_raw.csv') def green_shape(df): @@ -29,7 +29,7 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 - pass + return df.shape def green_dtypes(df): @@ -38,7 +38,7 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 - pass + return df.dtypes # ============================================================ @@ -52,7 +52,9 @@ def yellow_clean_columns(df): 提示:df.columns.str.strip().str.lower() """ # TODO: 你的程式碼 - pass + df_new = df.copy() + df_new.columns = df_new.columns.str.strip().str.lower() + return df_new def yellow_clean_amount(df): @@ -63,7 +65,15 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass + df_amount = df.copy() + df_amount['amount'] = ( + df_amount['amount'] + .astype(str) + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float) + ) + return df_amount def yellow_drop_duplicates(df): @@ -72,7 +82,7 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass + return df.drop_duplicates() # ============================================================ @@ -93,4 +103,10 @@ def red_clean_orders(path): 提示:pd.to_datetime(errors='coerce') """ # TODO: 你的程式碼 - pass + 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','amount']) # 只要這兩個欄位中任一個有缺失值,該列就會被剔除。 + df = df.drop_duplicates() + return df \ No newline at end of file diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..c0d829f 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -24,19 +24,27 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - pass + orders = pd.read_csv('datasets/ecommerce/orders_clean.csv') + customers = pd.read_csv('datasets/ecommerce/customers.csv') + products = pd.read_csv('datasets/ecommerce/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 + return len(df) def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" # TODO: 你的程式碼 - pass + return list(df) # ============================================================ @@ -50,7 +58,8 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - pass + category_rev = df.groupby('category')['amount'].sum().sort_values(ascending=False) + return category_rev.idxmax() def yellow_gold_vip_stats(df): @@ -60,7 +69,11 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - pass + gold_df = df[df['vip_level'] == 'Gold'] + order_count = int(len(gold_df)) + total_amount = float(gold_df['amount'].sum()) + + return (order_count, total_amount) def yellow_region_avg_amount(df): @@ -70,7 +83,13 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + region_mean = ( + df.groupby('region')['amount'] + .mean() + .round(2) + .sort_values(ascending=False) + ) + return region_mean # ============================================================ @@ -94,4 +113,11 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + rfm = df.groupby('customer_id').agg( + R=('order_date', 'max'), + F=('order_id', 'count'), + M=('amount', 'sum'), + customer_name=("customer_name", "first") + ).reset_index() + result = rfm.sort_values('M',ascending=False).head() + return result[["customer_id", "customer_name", "R", "F", "M"]] \ No newline at end of file diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..d037867 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -27,7 +27,9 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 - pass + df = _load_data() # 必須先讀取資料,否則會報錯 + ts = df.groupby(df['order_date'].dt.month) + return ts['amount'].mean() def green_top3_dates(): @@ -37,7 +39,8 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df = _load_data() + return df['order_date'].value_counts().head(3) def green_date_range(): @@ -46,7 +49,8 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 - pass + df = _load_data() + return df['order_date'].min(),df['order_date'].max() # ============================================================ @@ -60,7 +64,8 @@ def yellow_monthly_revenue(): 提示:set_index('order_date').resample('ME')['amount'].sum() """ # TODO: 你的程式碼 - pass + df = _load_data() + return df.set_index('order_date').resample('ME')['amount'].sum() def yellow_rolling_avg(monthly_revenue): @@ -71,7 +76,8 @@ def yellow_rolling_avg(monthly_revenue): 提示:.rolling(window=3).mean() """ # TODO: 你的程式碼 - pass + monthly_rev = yellow_monthly_revenue() + return monthly_rev.rolling(window=3).mean() def yellow_category_median(df): @@ -81,7 +87,13 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass + result = ( + df.groupby('category')['amount'] + .median() + .sort_values(ascending=False) + ) + + return result # ============================================================ @@ -101,4 +113,13 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + df = _load_data() + report = df.set_index('order_date').resample('ME').agg({ + 'order_id':'count', + 'amount':'sum', + 'customer_id':'nunique' + }) + report.columns=['order_count','revenue','active_customers'] + report['avg_order_value'] = report['revenue'] / report['order_count'] + report['revenue_growth'] = report['revenue'].pct_change() * 100 + return report \ No newline at end of file diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..80ca92f 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -29,7 +29,12 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ # TODO: 你的程式碼 - pass + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 4)) + # 計算訂單數最準確的方式是 countplot + sns.countplot(data=df, x='category', palette='viridis', ax=ax) + ax.set_title("Order Count by Category") + return fig def green_hist_amount(): @@ -39,7 +44,15 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 - pass + df = _load_data() + # 建立畫布與座標軸 + fig, ax = plt.subplots(figsize=(10, 4)) + + # 畫出直方圖,指定 bins=20 + sns.histplot(data=df, x='amount', bins=20, ax=ax, color='skyblue', kde=True) + + ax.set_title("Distribution of Order Amounts") + return fig def green_set_labels(): @@ -51,7 +64,13 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 4)) + fig = sns.barplot(data=df, x='region', y='amount', palette='viridis', hue='region', legend=False) + plt.title('This is title', fontweight='bold') + plt.xlabel('This is xlabel') + plt.ylabel('This is ylabel') + return fig # ============================================================ @@ -68,7 +87,16 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 - pass + df = _load_data() + df['month'] = df['order_date'].dt.to_period('M').astype(str) + trend = df.groupby(['month', 'region'])['amount'].sum().reset_index() + + trend = trend[trend['region'].isin(['North', 'South'])] + + fig, ax = plt.subplots(figsize=(10, 5)) + sns.lineplot(data=trend, x='month', y='amount', hue='region', marker='o', ax=ax) + plt.xticks(rotation=45) + return fig def yellow_box_vip(): @@ -78,7 +106,10 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 - pass + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + sns.boxplot(data=df, x='vip_level', y='amount', palette='Set2', ax=ax) + return fig def yellow_scatter_price_amount(): @@ -88,7 +119,13 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 - pass + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 6)) + + sns.scatterplot(data=df, x='unit_price', y='amount', alpha=0.6, ax=ax) + + ax.set_title("Unit Price vs. Total Amount") + return fig # ============================================================ @@ -107,4 +144,30 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 - pass + df = _load_data() + cat_df = df[df['category'] == category].copy() + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle(f"Dashboard: {category}", fontsize=20, fontweight='bold') + + # 1. 左上:月營收趨勢 + cat_df['month'] = cat_df['order_date'].dt.to_period('M').astype(str) + mon_rev = cat_df.groupby('month')['amount'].sum() + mon_rev.plot(kind='line', marker='o', ax=axes[0, 0], color='blue') + axes[0, 0].set_title("Monthly Revenue Trend") + + # 2. 右上:各地區營收 + sns.barplot(data=cat_df, x='region', y='amount', estimator=sum, ax=axes[0, 1]) + axes[0, 1].set_title("Revenue by Region") + + # 3. 左下:Top 5 商品 + top5 = cat_df.groupby('product_name')['amount'].sum().nlargest(5) + top5.plot(kind='barh', ax=axes[1, 0], color='green') + axes[1, 0].set_title("Top 5 Products") + + # 4. 右下:金額分佈 + sns.histplot(cat_df['amount'], bins=15, kde=True, ax=axes[1, 1], color='orange') + axes[1, 1].set_title("Order Amount Distribution") + + plt.tight_layout(rect=[0, 0.03, 1, 0.95]) + return fig \ No newline at end of file diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..9222dc3 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -26,7 +26,10 @@ def green_plotly_bar(): 提示:px.bar() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + rev_by_cat = df.groupby('category')['amount'].sum().reset_index() + fig = px.bar(rev_by_cat, x='category', y='amount', title="Revenue by Category") + return fig def green_plotly_line(): @@ -37,7 +40,11 @@ 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.to_period('M').astype(str) + monthly_rev = df.groupby('month')['amount'].sum().reset_index() + fig = px.line(monthly_rev, x='month', y='amount', title="Monthly Revenue Trend", markers=True) + return fig def green_plotly_pie(): @@ -48,7 +55,9 @@ def green_plotly_pie(): 提示:px.pie() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + fig = px.pie(df, names='vip_level', values='amount', title="Revenue Share by VIP Level") + return fig # ============================================================ @@ -62,8 +71,43 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 2. 合併 customers.csv 和 products.csv 回傳:合併後的 DataFrame """ - # TODO: 你的程式碼 - pass + # 讀取原始訂單資料 + orders = pd.read_csv(raw_path) + + # 標準化欄位名稱(去除空格,轉小寫) + orders.columns = orders.columns.str.strip().str.lower() + + # 重命名欄位 + orders = orders.rename(columns={ + 'order_id ': 'order_id', + 'product_id': 'product_id', + ' qty': 'qty' + }) + + # 清理金額:移除 $ 符號和逗號,轉為 float + orders['amount'] = orders['amount'].astype(str).str.replace('$', '').str.replace(',', '') + orders['amount'] = pd.to_numeric(orders['amount'], errors='coerce') + + # 解析日期 + orders['order_date'] = pd.to_datetime(orders['order_date'], errors='coerce') + + # 移除缺值 + orders = orders.dropna() + + # 去重 + orders = orders.drop_duplicates() + + # 讀取客戶資料和商品資料 + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) + + # 合併客戶資料 + orders = orders.merge(customers, on='customer_id', how='left') + + # 合併商品資料 + orders = orders.merge(products, on='product_id', how='left') + + return orders def yellow_kpi_summary(df): @@ -76,8 +120,12 @@ def yellow_kpi_summary(df): "avg_order_value": float, # 平均客單價 } """ - # TODO: 你的程式碼 - pass + return { + "total_revenue": float(df['amount'].sum()), + "order_count": len(df), + "active_customers": df['customer_id'].nunique(), + "avg_order_value": float(df['amount'].mean()), + } def yellow_plotly_scatter(df): @@ -90,8 +138,15 @@ def yellow_plotly_scatter(df): 回傳 plotly Figure 物件 提示:px.scatter(hover_data=['product_name']) """ - # TODO: 你的程式碼 - pass + fig = px.scatter( + df, + x='unit_price', + y='amount', + color='category', + hover_data=['product_name'], + title="Unit Price vs Order Amount by Category" + ) + return fig # ============================================================ @@ -114,5 +169,94 @@ def red_dashboard(): 回傳 plotly Figure 物件 提示:from plotly.subplots import make_subplots """ - # TODO: 你的程式碼 - pass + # 清理和合併資料 + df = yellow_clean_and_merge( + "datasets/ecommerce/orders_raw.csv", + "datasets/ecommerce/customers.csv", + "datasets/ecommerce/products.csv" + ) + + # 建立 2×2 subplot + fig = make_subplots( + rows=2, cols=2, + subplot_titles=( + "Monthly Revenue Trend", + "Top 10 Products by Revenue", + "Revenue by Region", + "Revenue Share by Category" + ), + specs=[ + [{"secondary_y": False}, {"secondary_y": False}], + [{"secondary_y": False}, {"type": "pie"}] + ] + ) + + # 1. 左上:月營收趨勢 (line) + df_copy = df.copy() + df_copy['month'] = df_copy['order_date'].dt.to_period('M').astype(str) + monthly_rev = df_copy.groupby('month')['amount'].sum().reset_index() + fig.add_trace( + go.Scatter( + x=monthly_rev['month'], + y=monthly_rev['amount'], + mode='lines+markers', + name='Monthly Revenue', + line=dict(color='#1f77b4') + ), + row=1, col=1 + ) + + # 2. 右上:Top 10 商品營收 (bar) + top10_products = df.groupby('product_name')['amount'].sum().nlargest(10).reset_index() + fig.add_trace( + go.Bar( + y=top10_products['product_name'], + x=top10_products['amount'], + orientation='h', + name='Product Revenue', + marker=dict(color='#ff7f0e') + ), + row=1, col=2 + ) + + # 3. 左下:各地區營收 (bar) + region_rev = df.groupby('region')['amount'].sum().reset_index() + fig.add_trace( + go.Bar( + x=region_rev['region'], + y=region_rev['amount'], + name='Region Revenue', + marker=dict(color='#2ca02c') + ), + row=2, col=1 + ) + + # 4. 右下:類別營收佔比 (pie) + category_rev = df.groupby('category')['amount'].sum().reset_index() + fig.add_trace( + go.Pie( + labels=category_rev['category'], + values=category_rev['amount'], + name='Category Share' + ), + row=2, col=2 + ) + + # 更新軸標籤 + fig.update_xaxes(title_text="Month", row=1, col=1) + fig.update_yaxes(title_text="Revenue ($)", row=1, col=1) + + fig.update_xaxes(title_text="Revenue ($)", row=1, col=2) + fig.update_yaxes(title_text="Product", row=1, col=2) + + fig.update_xaxes(title_text="Region", row=2, col=1) + fig.update_yaxes(title_text="Revenue ($)", row=2, col=1) + + # 設定整體標題 + fig.update_layout( + title_text="E-Commerce Dashboard", + height=900, + showlegend=True + ) + + return fig \ No newline at end of file