diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..f537df3 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,18 +19,24 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" # TODO: 你的程式碼 + arr = np.array([10, 20, 30, 40, 50]) + return float(np.mean(arr)) pass def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" # TODO: 你的程式碼 + arr = np.array([10, 20, 30, 40, 50]) + return arr * 2 pass def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" # TODO: 你的程式碼 + arr = np.array([10, 20, 30, 40, 50]) + return arr[arr > 25] pass @@ -42,6 +48,8 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 + count = np.sum(prices > 1000) + return int(count) pass @@ -51,6 +59,8 @@ def yellow_top3_stock_indices(stocks): 提示:np.argsort """ # TODO: 你的程式碼 + index = np.argsort(stocks)[::-1] + return index[:3] pass @@ -60,6 +70,10 @@ def yellow_restock_cost(prices, stocks): 提示:布林遮罩 + .sum() """ # TODO: 你的程式碼 + mask = prices < 500 + number = np.sum(mask) + totol_cost = np.sum(prices[mask] * 50) + return totol_cost pass @@ -77,4 +91,14 @@ def red_double11_prices(prices, stocks): 提示:np.where 可以巢狀使用 """ # TODO: 你的程式碼 + finall_price = np.where( + stocks >= 100, + prices * 0.7, + np.where( + stocks >= 20, + prices * 0.9, + prices + ) + ) + return finall_price pass diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..8dd41c5 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -20,6 +20,8 @@ def green_read_csv(): 提示:pd.read_csv() """ # TODO: 你的程式碼 + df = pd.read_csv("../datasets/ecommerce/orders_raw.csv") + return df pass @@ -29,6 +31,7 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 + return df.shape pass @@ -38,6 +41,7 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 + return df.dtypes pass @@ -52,6 +56,9 @@ def yellow_clean_columns(df): 提示:df.columns.str.strip().str.lower() """ # TODO: 你的程式碼 + new_df = df.copy() + new_df.columns = new_df.columns.str.strip().str.lower() + return new_df pass @@ -63,6 +70,9 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 + new_df = df.copy() + new_df['amount'] = new_df['amount'].str.replace('$', '', regex=False).str.replace(',', '',regex=False).astype(float) + return new_df pass @@ -72,6 +82,7 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 + return df.drop_duplicates() pass @@ -93,4 +104,11 @@ def red_clean_orders(path): 提示:pd.to_datetime(errors='coerce') """ # TODO: 你的程式碼 + df = pd.read_csv(path) + df.columns = df.columns.str.strip().str.lower() + df['amount'] = df['amount'].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=['amount', 'order_date'], how='any') + df = df.drop_duplicates() + return df pass diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..c31dc5a 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -24,18 +24,26 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 + 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') + merged_df = pd.merge(orders, customers, on='customer_id', how='left') + final_df = pd.merge(merged_df, products, on='product_id', how='left') + return final_df pass def green_row_count(df): """回傳 DataFrame 的列數 (int)""" # TODO: 你的程式碼 + return len(df) pass def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" # TODO: 你的程式碼 + return df.columns.tolist() pass @@ -50,6 +58,9 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 + category_revenue = df.groupby('category')['amount'].sum() + top_cat = category_revenue.idxmax() + return str(top_cat) pass @@ -60,6 +71,10 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 + gold_df = df[df['vip_level'] == 'Gold'] + order_count = len(gold_df) + total_amount = gold_df['amount'].sum() + return int(order_count), float(total_amount) pass @@ -70,6 +85,8 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 + region_avg = df.groupby('region')['amount'].mean() + return region_avg pass @@ -94,4 +111,11 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 + rfm = df.groupby(['customer_id', 'customer_name']).agg( + R=('order_date', 'max'), + F=('order_date', 'count'), + M=('amount', 'sum') + ).reset_index() + rfm_sorted = rfm.sort_values(by='M', ascending=False) + return rfm_sorted.head(5) pass diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..afb5e70 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -11,7 +11,7 @@ def _load_data(): """輔助函式:讀取並解析日期""" - df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) return df @@ -27,6 +27,9 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 + df = _load_data() + monthly_avg = df.groupby(df['order_date'].dt.month)['amount'].mean() + return monthly_avg pass @@ -37,6 +40,9 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 + df = _load_data() + date_counts = df['order_date'].value_counts() + return date_counts.head(3) pass @@ -46,6 +52,10 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 + df = _load_data() + min_date = df['order_date'].min() + max_date = df['order_date'].max() + return (min_date, max_date) pass @@ -60,6 +70,9 @@ def yellow_monthly_revenue(): 提示:set_index('order_date').resample('ME')['amount'].sum() """ # TODO: 你的程式碼 + df = _load_data() + monthly_rev = df.set_index('order_date').resample('ME')['amount'].sum() + return monthly_rev pass @@ -71,6 +84,8 @@ def yellow_rolling_avg(monthly_revenue): 提示:.rolling(window=3).mean() """ # TODO: 你的程式碼 + rolling_avg = monthly_revenue.rolling(window=3).mean() + return rolling_avg pass @@ -81,6 +96,8 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 + category_median = df.groupby('category')['amount'].median().sort_values(ascending=False) + return category_median pass @@ -101,4 +118,14 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 + df = _load_data() + df = df.set_index('order_date') + report = df.resample('ME').agg( + order_count=('amount', 'count'), + revenue=('amount', 'sum'), + active_customers=('customer_id', 'nunique') + ) + report['avg_order_value'] = report['revenue'] / report['order_count'] + report['revenue_growth'] = report['revenue'].pct_change() + return report pass diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..a8eb0e6 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -14,7 +14,7 @@ def _load_data(): """輔助函式:讀取資料""" - return pd.read_csv("datasets/ecommerce/orders_enriched.csv", + return pd.read_csv("../datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) @@ -29,6 +29,11 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(10, 6)) + sns.countplot(data=df, x='category', ax=ax) + ax.set_title("Order Count by Category") + return fig pass @@ -39,6 +44,11 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(10, 6)) + sns.histplot(data=df, x='amount', bins=20, ax=ax) + ax.set_title("Distribution of Order Amounts") + return fig pass @@ -51,6 +61,13 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 + data = {'A': 100, 'B': 200, 'C': 300} + fig, ax = plt.subplots() + ax.bar(data.keys(), data.values()) + ax.set_title("Simple Bar Chart") + ax.set_xlabel("Category") + ax.set_ylabel("Values") + return fig pass @@ -68,6 +85,15 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 + df = _load_data() + target_regions = df[df['region'].isin(['North', 'South'])] + trend_data = target_regions.groupby([target_regions['order_date'].dt.month, 'region'])['amount'].sum().unstack() + fig, ax = plt.subplots(figsize=(10, 6)) + sns.lineplot(data=target_regions, x=target_regions['order_date'].dt.month, y='amount', hue='region', estimator='sum', ax=ax) + ax.set_title("Monthly Revenue Trend: North vs South") + ax.set_xlabel("Month") + ax.set_ylabel("Total Revenue") + return fig pass @@ -78,6 +104,11 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(10, 6)) + sns.boxplot(data=df, x='vip_level', y='amount', ax=ax) + ax.set_title("Order Amount Distribution by VIP Level") + return fig pass @@ -88,6 +119,11 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(10, 6)) + sns.scatterplot(data=df, x='unit_price', y='amount', ax=ax) + ax.set_title("Unit Price vs Order Amount") + return fig pass @@ -107,4 +143,26 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 + 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) + monthly_rev = cat_df.set_index('order_date').resample('ME')['amount'].sum() + axes[0, 0].plot(monthly_rev.index, monthly_rev.values, marker='o', color='b') + axes[0, 0].set_title("Monthly Revenue Trend") + axes[0, 0].tick_params(axis='x', rotation=45) + + region_rev = cat_df.groupby('region')['amount'].sum().sort_values(ascending=False) + sns.barplot(x=region_rev.index, y=region_rev.values, ax=axes[0, 1]) + axes[0, 1].set_title("Revenue by Region") + + top5_prod = cat_df.groupby('product_name')['amount'].sum().nlargest(5) + sns.barplot(x=top5_prod.values, y=top5_prod.index, ax=axes[1, 0], orient='h') + axes[1, 0].set_title("Top 5 Products by Revenue") + + sns.histplot(cat_df['amount'], bins=15, kde=True, ax=axes[1, 1]) + axes[1, 1].set_title("Order Amount Distribution") + + plt.tight_layout(rect=[0, 0.03, 1, 0.95]) + return fig pass diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..8e24f3b 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -26,6 +26,16 @@ def green_plotly_bar(): 提示:px.bar() """ # TODO: 你的程式碼 + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv") + cat_revenue = df.groupby('category')['amount'].sum().reset_index() + fig = px.bar( + cat_revenue, + x='category', + y='amount', + title='Total Revenue by Category', + labels={'amount': 'Total Revenue', 'category': 'Product Category'}, + template='plotly_white') + return fig pass @@ -37,6 +47,16 @@ def green_plotly_line(): 提示:先 groupby 月份算總營收,再 px.line() """ # TODO: 你的程式碼 + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv") + monthly_rev = df.set_index('order_date').resample('ME')['amount'].sum().reset_index() + fig = px.line( + monthly_rev, + x='order_date', + y='amount', + title='Monthly Revenue Trend', + labels={'order_date': 'Month', 'amount': 'Monthly Revenue'}, + markers=True) + return fig pass @@ -48,6 +68,13 @@ def green_plotly_pie(): 提示:px.pie() """ # TODO: 你的程式碼 + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv") + fig = px.pie( + df, + names='vip_level', + title='Order Distribution by VIP Level', + hole=0.3) + return fig pass @@ -59,11 +86,26 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): """ 完整 ETL:從髒資料到合併完成的 DataFrame 1. 讀取 orders_raw.csv 並清理(欄位名稱、金額、日期、缺值、去重) - 2. 合併 customers.csv 和 products.csv + 2. 統一欄位大小寫以解決 Merge 衝突 + 3. 合併 customers.csv 和 products.csv 回傳:合併後的 DataFrame """ # TODO: 你的程式碼 - pass + orders = pd.read_csv(raw_path) + orders = orders.drop_duplicates() + orders = orders.dropna(subset=['order_id', 'customer_id']) + orders.columns = orders.columns.str.lower() + orders['order_date'] = pd.to_datetime(orders['order_date']) + if 'amount' in orders.columns: + orders['amount'] = pd.to_numeric(orders['amount'], errors='coerce').fillna(0) + customers = pd.read_csv(customers_path) + customers.columns = customers.columns.str.lower() + products = pd.read_csv(products_path) + products.columns = products.columns.str.lower() + df_merged = orders.merge(customers, on='customer_id', how='left') + df_merged = df_merged.merge(products, on='product_id', how='left') + + return df_merged def yellow_kpi_summary(df): @@ -77,6 +119,16 @@ def yellow_kpi_summary(df): } """ # TODO: 你的程式碼 + total_revenue = float(df['amount'].sum()) + order_count = int(df['order_id'].count()) + active_customers = int(df['customer_id'].nunique()) + avg_order_value = total_revenue / order_count if order_count > 0 else 0.0 + return { + "total_revenue": total_revenue, + "order_count": order_count, + "active_customers": active_customers, + "avg_order_value": avg_order_value + } pass @@ -91,6 +143,21 @@ def yellow_plotly_scatter(df): 提示:px.scatter(hover_data=['product_name']) """ # TODO: 你的程式碼 + fig = px.scatter( + df, + x='unit_price', + y='amount', + color='category', + hover_data=['product_name'], + title='Product Price vs Order Amount by Category', + labels={ + 'unit_price': 'Unit Price ($)', + 'amount': 'Order Amount ($)', + 'category': 'Product Category' + }, + template='plotly_white' + ) + return fig pass @@ -115,4 +182,65 @@ def red_dashboard(): 提示:from plotly.subplots import make_subplots """ # TODO: 你的程式碼 + orders = pd.read_csv("../datasets/ecommerce/orders_raw.csv").drop_duplicates() + orders.columns = orders.columns.str.lower() + orders['order_date'] = pd.to_datetime(orders['order_date']) + customers = pd.read_csv("../datasets/ecommerce/customers.csv") + customers.columns = customers.columns.str.lower() + products = pd.read_csv("../datasets/ecommerce/products.csv") + products.columns = products.columns.str.lower() + df = orders.merge(customers, on='customer_id', how='left') + df = df.merge(products, on='product_id', how='left') + + # 1. 強制將 amount 轉為數字,無法轉換的會變成 NaN + df['amount'] = pd.to_numeric(df['amount'], errors='coerce') + + # 2. 處理可能出現的空值(選用,建議補 0 避免計算錯誤) + df['amount'] = df['amount'].fillna(0) + + 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=[[{"type": "xy"}, {"type": "xy"}], + [{"type": "xy"}, {"type": "domain"}]] + ) + + monthly_rev = df.set_index('order_date').resample('ME')['amount'].sum().reset_index() + fig.add_trace( + go.Scatter(x=monthly_rev['order_date'], y=monthly_rev['amount'], mode='lines+markers', name='Revenue'), + row=1, col=1 + ) + + top10_prod = df.groupby('product_name')['amount'].sum().nlargest(10).reset_index() + fig.add_trace( + go.Bar(x=top10_prod['product_name'], y=top10_prod['amount'], name='Product Rev'), + row=1, col=2 + ) + + region_rev = df.groupby('region')['amount'].sum().reset_index() + fig.add_trace( + go.Bar(x=region_rev['region'], y=region_rev['amount'], name='Region Rev'), + row=2, col=1 + ) + + cat_rev = df.groupby('category')['amount'].sum().reset_index() + fig.add_trace( + go.Pie(labels=cat_rev['category'], values=cat_rev['amount'], hole=0.4, name='Category %'), + row=2, col=2 + ) + + fig.update_layout( + height=800, + width=1000, + title_text="E-commerce Business Overview Dashboard", + showlegend=False, + template="plotly_white" + ) + fig.update_xaxes(tickangle=45) + return fig pass