diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..a90c236 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -11,7 +11,6 @@ """ import numpy as np - # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ @@ -19,30 +18,38 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + return arr.mean() def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + result = arr * 2 + return result def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" # TODO: 你的程式碼 - pass - + arr = np.array([10, 20, 30, 40, 50]) + result = arr[arr > 25] + return result # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # 以下函式會接收從 products.csv 讀出的 prices, stocks 陣列 # ============================================================ +# ---------- 載入 products.csv ---------- +DATA_PATH = "datasets/ecommerce/products.csv" +prices = np.genfromtxt(DATA_PATH, delimiter=",", skip_header=1, usecols=3) +stocks = np.genfromtxt(DATA_PATH, delimiter=",", skip_header=1, usecols=4) + def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" - # TODO: 你的程式碼 - pass + return prices[prices>1000].size def yellow_top3_stock_indices(stocks): @@ -50,8 +57,7 @@ def yellow_top3_stock_indices(stocks): 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) 提示:np.argsort """ - # TODO: 你的程式碼 - pass + return np.argsort(stocks)[::-1][:3] def yellow_restock_cost(prices, stocks): @@ -59,14 +65,13 @@ def yellow_restock_cost(prices, stocks): 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) 提示:布林遮罩 + .sum() """ - # TODO: 你的程式碼 - pass - + return (prices[prices < 500] * 50).sum() # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ + def red_double11_prices(prices, stocks): """ 雙 11 定價規則(必須向量化,不能用 for-loop): @@ -76,5 +81,5 @@ def red_double11_prices(prices, stocks): 回傳每個商品的雙 11 售價 (ndarray) 提示:np.where 可以巢狀使用 """ - # TODO: 你的程式碼 - pass + return np.where(stocks >= 100, prices * 0.7, + np.where((stocks >= 20) & (stocks <= 99), prices * 0.9, prices)) diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..1a9bdba 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -9,18 +9,20 @@ """ import pandas as pd - # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ +DATA = '../datasets/ecommerce/orders_raw.csv' +df = pd.read_csv(DATA) + def green_read_csv(): """ 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) 提示:pd.read_csv() """ # TODO: 你的程式碼 - pass + return df def green_shape(df): @@ -29,7 +31,8 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 - pass + return df.shape + def green_dtypes(df): @@ -38,7 +41,7 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 - pass + return df.dtypes # ============================================================ @@ -52,7 +55,9 @@ def yellow_clean_columns(df): 提示:df.columns.str.strip().str.lower() """ # TODO: 你的程式碼 - pass + new_df = df.copy() + new_df.columns = new_df.columns.str.strip().str.lower() + return new_df.columns def yellow_clean_amount(df): @@ -63,7 +68,15 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass + df_copy = df.copy() # 複製一份 + df_copy['amount']=( + df_copy['amount'] + .astype(str) + .str.replace('$', '', regex = False) + .str.replace(',', '', regex = False) + .astype(float) + ) + return df_copy # 整張清理過的表 def yellow_drop_duplicates(df): @@ -72,7 +85,8 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass + df_drop = df.drop_duplicates() + return df_drop # ============================================================ @@ -93,4 +107,23 @@ def red_clean_orders(path): 提示:pd.to_datetime(errors='coerce') """ # TODO: 你的程式碼 - pass + df = pd.read_csv(path) # 1 + + df.columns = df.columns.str.strip().str.lower() # 2 + + df['amount']=( + df['amount'] + .astype(str) + .str.replace('$', '', regex = False) + .str.replace(',', '', regex = False) + .astype(float) + ) # 3 + + df['order_date'] = pd.to_datetime(df['order_date'], errors = 'coerce') # 4 + + df = df.dropna(subset = ['amount','order_date']) # 5 + + df_drop = df.drop_duplicates() # 6 + df = df_drop + return df + diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..7b0b18d 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -15,6 +15,11 @@ # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ +order = pd.read_csv('..\datasets\ecommerce\orders_clean.csv') +customers = pd.read_csv('..\datasets\ecommerce\customers.csv') +products = pd.read_csv('..\datasets\ecommerce\products.csv') + + def green_load_and_merge(): """ @@ -24,19 +29,22 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - pass + oc = order.merge(customers, on = 'customer_id',how = 'left' ) + df = oc.merge(products, on = 'product_id', how = 'left') + return df def green_row_count(df): """回傳 DataFrame 的列數 (int)""" # TODO: 你的程式碼 - pass - + count = df.shape[0] + return count def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" # TODO: 你的程式碼 - pass + column_names = df.columns.tolist() + return column_names # ============================================================ @@ -50,7 +58,8 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - pass + category_rev = df.groupby('category')['amount'].sum().idxmax() + return category_rev def yellow_gold_vip_stats(df): @@ -60,7 +69,10 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - pass + gold_df = df[df['vip_level'] == 'Gold'] + order_count = len(gold_df) # 數出vip是gold等級的有幾張訂單 + gold_amount = gold_df['amount'].sum() + return (int(order_count), float(gold_amount)) def yellow_region_avg_amount(df): @@ -70,7 +82,8 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + region_avg = df.groupby('region')['amount'].mean() + return region_avg # ============================================================ @@ -94,4 +107,14 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + rfm = df.groupby(['customer_id', 'customer_name']).agg({ + 'order_date': 'max', + 'order_id': 'count', + 'amount': 'sum' + }).reset_index() + + rfm.columns = ['customer_id', 'customer_name', 'R', 'F', 'M'] + + top5 = rfm.sort_values(by='M', ascending=False).head(5) + + return top5 diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..ae8e196 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() + mon_avg = df.groupby(df['order_date'].dt.month)['amount'].mean() + return mon_avg def green_top3_dates(): @@ -37,7 +39,9 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df = _load_data() + result = df['order_id'].value_counts().head(3) + return result def green_date_range(): @@ -46,7 +50,11 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 - pass + df = _load_data() + earliest = df['order_date'].min() + latest = df['order_date'].max() + + return (earliest, latest) # ============================================================ @@ -60,7 +68,9 @@ def yellow_monthly_revenue(): 提示:set_index('order_date').resample('ME')['amount'].sum() """ # TODO: 你的程式碼 - pass + df = _load_data() + monthly_revenue = df.set_index('order_date').resample('ME')['amount'].sum() + return monthly_revenue def yellow_rolling_avg(monthly_revenue): @@ -71,7 +81,8 @@ def yellow_rolling_avg(monthly_revenue): 提示:.rolling(window=3).mean() """ # TODO: 你的程式碼 - pass + rolling_avg = monthly_revenue.rolling(window=3).mean() + return rolling_avg def yellow_category_median(df): @@ -81,7 +92,9 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass + df = _load_data() + category_med = df.groupby('category')['amount'].median().sort_values(ascending=False) + return category_med # ============================================================ @@ -101,4 +114,19 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + df = _load_data + temp_df = df.set_index('order_date') + + report = temp_df.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() + + return report diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..8e7909b 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -29,7 +29,10 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ # TODO: 你的程式碼 - pass + df = _load_data() + fig, ax = plt.subplots() + sns.countplot(x='category', data=df, ax=ax) + return fig def green_hist_amount(): @@ -39,7 +42,10 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 - pass + df = _load_data() + fig, ax = plt.subplots() + sns.histplot(df['amount'], bins=20, ax=ax) + return fig def green_set_labels(): @@ -51,7 +57,13 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass + df = _load_data() + fig, ax = plt.subplots() + df['category'].value_counts().plot(kind='bar', ax=ax) + ax.set_title("Order Count by Category") + ax.set_xlabel("Product Category") + ax.set_ylabel("Number of Orders") + return fig # ============================================================ @@ -68,7 +80,14 @@ 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[df['region'].isin(['North', 'South'])].groupby(['month', 'region'])['amount'].sum().reset_index() + + fig, ax = plt.subplots(figsize=(10, 6)) + sns.lineplot(x='month', y='amount', hue='region', data=trend, ax=ax) + plt.xticks(rotation=45) + return fig def yellow_box_vip(): @@ -78,7 +97,10 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 - pass + df = _load_data() + fig, ax = plt.subplots() + sns.boxplot(x='vip_level', y='amount', data=df, ax=ax) + return fig def yellow_scatter_price_amount(): @@ -88,7 +110,10 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 - pass + df = _load_data() + fig, ax = plt.subplots() + sns.scatterplot(x='unit_price', y='amount', data=df, ax=ax) + return fig # ============================================================ @@ -107,4 +132,32 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 - pass + df = _load_data() + target_df = df[df['category'] == category].copy() + target_df['month'] = target_df['order_date'].dt.to_period('M').astype(str) + + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle(f"Dashboard: {category}", fontsize=20) + + + monthly_rev = target_df.groupby('month')['amount'].sum() + monthly_rev.plot(ax=axes[0, 0], marker='o') + axes[0, 0].set_title("Monthly Revenue Trend") + + + region_rev = target_df.groupby('region')['amount'].sum() + region_rev.plot(kind='bar', ax=axes[0, 1]) + axes[0, 1].set_title("Revenue by Region") + + + top5 = target_df.groupby('product_name')['amount'].sum().sort_values(ascending=False).head(5) + top5.plot(kind='barh', ax=axes[1, 0]) + axes[1, 0].set_title("Top 5 Products") + + + sns.histplot(target_df['amount'], bins=15, ax=axes[1, 1]) + axes[1, 1].set_title("Order Amount Distribution") + + plt.tight_layout(rect=[0, 0.03, 1, 0.95]) + return fig diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..405b4bf 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -26,7 +26,13 @@ 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 +43,12 @@ def green_plotly_line(): 提示:先 groupby 月份算總營收,再 px.line() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) + + 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") + return fig def green_plotly_pie(): @@ -48,7 +59,10 @@ def green_plotly_pie(): 提示:px.pie() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + + fig = px.pie(df, names='vip_level', title="VIP Order Distribution") + return fig # ============================================================ @@ -63,7 +77,15 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 回傳:合併後的 DataFrame """ # TODO: 你的程式碼 - pass + orders = pd.read_csv(raw_path, parse_dates=["order_date"]) + orders = orders.dropna().drop_duplicates() # 去缺值與重複 + + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) + + df = orders.merge(customers, on='customer_id', how='left') + df = df.merge(products, on='product_id', how='left') + return df def yellow_kpi_summary(df): @@ -77,7 +99,13 @@ def yellow_kpi_summary(df): } """ # TODO: 你的程式碼 - pass + kpi = { + "total_revenue": float(df['amount'].sum()), + "order_count": int(df['order_id'].nunique()), + "active_customers": int(df['customer_id'].nunique()), + "avg_order_value": float(df['amount'].sum() / df['order_id'].nunique()) + } + return kpi def yellow_plotly_scatter(df): @@ -91,7 +119,9 @@ 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'], title="Price vs Amount") + return fig # ============================================================ @@ -115,4 +145,31 @@ def red_dashboard(): 提示: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. 準備 2x2 畫布,並設定子圖類型 (pie 圖需要特別設定 type='domain') + fig = make_subplots( + rows=2, cols=2, + subplot_titles=("Monthly Revenue", "Top 10 Products", "Region Revenue", "Category Share"), + specs=[[{"type": "xy"}, {"type": "xy"}], + [{"type": "xy"}, {"type": "domain"}]] + ) + + m_rev = df.set_index('order_date').resample('ME')['amount'].sum().reset_index() + fig.add_trace(go.Scatter(x=m_rev['order_date'], y=m_rev['amount'], name="Revenue"), row=1, col=1) + + top10 = df.groupby('product_name')['amount'].sum().sort_values(ascending=False).head(10) + fig.add_trace(go.Bar(x=top10.index, y=top10.values, name="Top Products"), row=1, col=2) + + reg_rev = df.groupby('region')['amount'].sum() + fig.add_trace(go.Bar(x=reg_rev.index, y=reg_rev.values, name="Region"), row=2, col=1) + + cat_rev = df.groupby('category')['amount'].sum() + fig.add_trace(go.Pie(labels=cat_rev.index, values=cat_rev.values), row=2, col=2) + + fig.update_layout(height=800, title_text="E-commerce Executive Dashboard", showlegend=False) + return fig \ No newline at end of file