diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..3cfe7aa 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -18,31 +18,25 @@ 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]) + 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] -# ============================================================ -# 🟡 核心題(每題 15 分,共 45 分) -# 以下函式會接收從 products.csv 讀出的 prices, stocks 陣列 -# ============================================================ - def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" - # TODO: 你的程式碼 - pass + return np.sum(prices > 1000) def yellow_top3_stock_indices(stocks): @@ -50,8 +44,7 @@ def yellow_top3_stock_indices(stocks): 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) 提示:np.argsort """ - # TODO: 你的程式碼 - pass + return np.argsort(stocks)[-3:][::-1] def yellow_restock_cost(prices, stocks): @@ -59,14 +52,9 @@ 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 +64,8 @@ def red_double11_prices(prices, stocks): 回傳每個商品的雙 11 售價 (ndarray) 提示:np.where 可以巢狀使用 """ - # TODO: 你的程式碼 - pass + return np.where( + stocks >= 100, + prices * 0.7, + np.where(stocks >= 20, prices * 0.9, prices) + ) \ No newline at end of file diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..f0563a7 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -20,8 +20,8 @@ def green_read_csv(): 提示:pd.read_csv() """ # TODO: 你的程式碼 - pass - +def green_read_csv(): + return pd.read_csv("datasets/ecommerce/orders_raw.csv") def green_shape(df): """ @@ -29,7 +29,8 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 - pass +def green_shape(df): + return df.shape def green_dtypes(df): @@ -38,7 +39,8 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 - pass +def green_dtypes(df): + return df.dtypes # ============================================================ @@ -52,8 +54,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 def yellow_clean_amount(df): """ @@ -63,8 +66,14 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass - + new_df = df.copy() + new_df["amount"] = ( + new_df["amount"] + .str.replace("$", "", regex=False) + .str.replace(",", "", regex=False) + .astype(float) + ) + return new_df def yellow_drop_duplicates(df): """ @@ -72,8 +81,7 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass - + return df.drop_duplicates() # ============================================================ # 🔴 挑戰題(25 分) @@ -93,4 +101,16 @@ 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"] + .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"]) + 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..df9992f 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -24,19 +24,26 @@ 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 = pd.merge(orders, customers, on="customer_id", how="left") + df = pd.merge(df, 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 df.columns.tolist() # ============================================================ @@ -50,7 +57,7 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - pass + return df.groupby("category")["amount"].sum().idxmax() def yellow_gold_vip_stats(df): @@ -60,7 +67,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) + total_amount = gold_df["amount"].sum() + return (order_count, total_amount) def yellow_region_avg_amount(df): @@ -70,7 +80,7 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + return df.groupby("region")["amount"].mean() # ============================================================ @@ -94,4 +104,18 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + df = df.copy() + df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce") + + rfm = df.groupby("customer_id").agg( + R=("order_date", "max"), + F=("order_id", "count"), + M=("amount", "sum") + ).reset_index() + + names = df[["customer_id", "customer_name"]].drop_duplicates() + rfm = pd.merge(rfm, names, on="customer_id", how="left") + rfm = rfm.sort_values(by="M", ascending=False).head(5) + rfm = rfm[["customer_id", "customer_name", "R", "F", "M"]] + + return rfm \ No newline at end of file diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..568f547 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -27,7 +27,8 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 - pass + df = _load_data() + return df.groupby(df["order_date"].dt.month)["amount"].mean() def green_top3_dates(): @@ -37,7 +38,8 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df = _load_data() + return df["order_date"].dt.date.value_counts().head(3) def green_date_range(): @@ -46,7 +48,8 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 - pass + df = _load_data() + return (df["order_date"].min(), df["order_date"].max()) # ============================================================ @@ -60,7 +63,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 +75,7 @@ def yellow_rolling_avg(monthly_revenue): 提示:.rolling(window=3).mean() """ # TODO: 你的程式碼 - pass + return monthly_revenue.rolling(window=3).mean() def yellow_category_median(df): @@ -81,7 +85,11 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass + return ( + df.groupby("category")["amount"] + .median() + .sort_values(ascending=False) + ) # ============================================================ @@ -101,4 +109,4 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..542cf3a 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -11,7 +11,6 @@ import pandas as pd import seaborn as sns - def _load_data(): """輔助函式:讀取資料""" return pd.read_csv("datasets/ecommerce/orders_enriched.csv", @@ -29,7 +28,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 +41,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,8 +56,12 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass - + fig, ax = plt.subplots() + ax.bar(["A", "B", "C"], [10, 20, 15]) + ax.set_title("Sample Bar Chart") + ax.set_xlabel("Category") + ax.set_ylabel("Value") + return fig # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) @@ -68,7 +77,20 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 - pass + df = _load_data() + + monthly = ( + df.set_index("order_date") + .groupby("region") + .resample("ME")["amount"] + .sum() + .reset_index() + ) + monthly = monthly[monthly["region"].isin(["North", "South"])] + fig, ax = plt.subplots() + sns.lineplot(data=monthly, x="order_date", y="amount", + hue="region", ax=ax) + return fig def yellow_box_vip(): @@ -78,7 +100,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 +113,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 +135,3 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 - pass diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..386fe4a 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -26,7 +26,11 @@ def green_plotly_bar(): 提示:px.bar() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + agg = df.groupby("category")["amount"].sum().reset_index() + fig = px.bar(agg, x="category", y="amount") + return fig def green_plotly_line(): @@ -37,7 +41,16 @@ def green_plotly_line(): 提示:先 groupby 月份算總營收,再 px.line() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + monthly = ( + df.set_index("order_date") + .resample("ME")["amount"] + .sum() + .reset_index() + ) + fig = px.line(monthly, x="order_date", y="amount") + return fig def green_plotly_pie(): @@ -48,7 +61,12 @@ def green_plotly_pie(): 提示:px.pie() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + counts = df["vip_level"].value_counts().reset_index() + counts.columns = ["vip_level", "count"] + fig = px.pie(counts, names="vip_level", values="count") + return fig # ============================================================ @@ -63,7 +81,24 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 回傳:合併後的 DataFrame """ # TODO: 你的程式碼 - pass + df = pd.read_csv(raw_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"]) + df = df.drop_duplicates() + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) + + df = pd.merge(df, customers, on="customer_id", how="left") + df = pd.merge(df, products, on="product_id", how="left") + + return df def yellow_kpi_summary(df): @@ -77,7 +112,17 @@ def yellow_kpi_summary(df): } """ # TODO: 你的程式碼 - pass + total_revenue = df["amount"].sum() + order_count = len(df) + active_customers = df["customer_id"].nunique() + avg_order_value = total_revenue / order_count if order_count > 0 else 0 + + return { + "total_revenue": float(total_revenue), + "order_count": int(order_count), + "active_customers": int(active_customers), + "avg_order_value": float(avg_order_value), + } def yellow_plotly_scatter(df): @@ -91,7 +136,14 @@ 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"] + ) + return fig # ============================================================ @@ -115,4 +167,5 @@ def red_dashboard(): 提示:from plotly.subplots import make_subplots """ # TODO: 你的程式碼 - pass + + \ No newline at end of file