From c93bd303897ad71add1bf8e7effafe2eb88690ec Mon Sep 17 00:00:00 2001 From: Jerry Date: Fri, 8 May 2026 08:56:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C=E6=A5=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 26 ++++-- homework/m2_pandas_cleaning.py | 27 ++++-- homework/m3_pandas_advanced.py | 37 ++++++-- homework/m4_timeseries.py | 38 ++++++-- homework/m5_visualization.py | 142 ++++++++++++++++++++++++++++-- homework/m6_plotly_capstone.py | 155 +++++++++++++++++++++++++++++++-- 6 files changed, 383 insertions(+), 42 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..dfb6196 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,19 +19,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] # ============================================================ @@ -42,7 +48,7 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 - pass + return (prices > 1000).sum() def yellow_top3_stock_indices(stocks): @@ -51,7 +57,7 @@ def yellow_top3_stock_indices(stocks): 提示:np.argsort """ # TODO: 你的程式碼 - pass + return np.argsort(-stocks)[0:3] def yellow_restock_cost(prices, stocks): @@ -60,7 +66,9 @@ def yellow_restock_cost(prices, stocks): 提示:布林遮罩 + .sum() """ # TODO: 你的程式碼 - pass + product = prices[prices < 500] + + return (product * 50).sum() # ============================================================ @@ -77,4 +85,8 @@ def red_double11_prices(prices, stocks): 提示: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..04e9ef8 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,10 @@ def yellow_clean_columns(df): 提示:df.columns.str.strip().str.lower() """ # TODO: 你的程式碼 - pass + clean = df.copy() + clean.columns = clean.columns.str.strip().str.lower() + + return clean def yellow_clean_amount(df): @@ -63,7 +66,10 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass + clean = df.copy() + clean["amount"] = clean["amount"].str.replace("$", "", regex=False).str.replace(",", "", regex=False).astype(float) + + return clean def yellow_drop_duplicates(df): @@ -72,7 +78,7 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass + return df.drop_duplicates() # ============================================================ @@ -93,4 +99,11 @@ 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..afa4d8f 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 = orders.merge(customers, on="customer_id", how="left") + df = df.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.columns) # ============================================================ @@ -50,7 +57,9 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - pass + revenue = df.groupby("category")["amount"].sum().idxmax() + + return revenue def yellow_gold_vip_stats(df): @@ -60,7 +69,10 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - pass + orders = df[df["vip_level"] == "Gold"]["order_id"].count() + total = df[df["vip_level"] == "Gold"]["amount"].sum() + + return (orders, total) def yellow_region_avg_amount(df): @@ -70,7 +82,9 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + average = df.groupby("region")["amount"].mean() + + return average # ============================================================ @@ -94,4 +108,13 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + rfm = df.groupby("customer_id").agg( + customer_name=("customer_name", "first"), + R=("order_date", "max"), + F=("order_id", "count"), + M=("amount", "sum") + ).reset_index() + + top5 = rfm.sort_values("M", ascending=False).head(5)[["customer_id", "customer_name", "R", "F", "M"]] + + return top5 \ No newline at end of file diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..496e209 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -27,7 +27,10 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 - pass + df = _load_data() + month = df["order_date"].dt.month + + return df.groupby(month)["amount"].mean().round(1) def green_top3_dates(): @@ -37,7 +40,10 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df = _load_data() + top3 = df["order_date"].dt.date.value_counts().head(3) + + return top3 def green_date_range(): @@ -46,7 +52,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 +70,10 @@ def yellow_monthly_revenue(): 提示:set_index('order_date').resample('ME')['amount'].sum() """ # TODO: 你的程式碼 - pass + df = _load_data() + monthly = df.set_index("order_date").resample("ME")["amount"].sum() + + return monthly def yellow_rolling_avg(monthly_revenue): @@ -71,7 +84,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 +94,7 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass + return df.groupby("category")["amount"].median().sort_values(ascending=False) # ============================================================ @@ -101,4 +114,15 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + df = _load_data() + + monthly = df.set_index("order_date").resample("ME").agg( + order_count=("order_id", "count"), + revenue=("amount", "sum"), + active_customers=("customer_id", "nunique") + ) + + monthly["avg_order_value"] = monthly["revenue"] / monthly["order_count"] + monthly["revenue_growth"] = monthly["revenue"].pct_change() + + return monthly \ No newline at end of file diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..f7fa554 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -29,7 +29,19 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ # TODO: 你的程式碼 - pass + df = _load_data() + + fig, ax = plt.subplots(figsize=(8, 4)) + + sns.countplot(data=df, x="category", ax=ax) + + ax.set_title("Order Count by Category") + ax.set_xlabel("Category") + ax.set_ylabel("Order Count") + + fig.tight_layout() + + return fig def green_hist_amount(): @@ -39,7 +51,19 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 - pass + df = _load_data() + + fig, ax = plt.subplots(figsize=(8, 4)) + + sns.histplot(data=df, x="amount", bins=20, ax=ax) + + ax.set_title("Distribution of Order Amount") + ax.set_xlabel("Amount") + ax.set_ylabel("Count") + + fig.tight_layout() + + return fig def green_set_labels(): @@ -51,7 +75,23 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass + fig, ax = plt.subplots() + + brands = ["Samsung", "Apple", "Xiaomi", "OPPO", "vivo", "Others"] + market_share = [22, 20, 11, 10, 7, 29] + + ax.bar(brands, market_share) + ax.set_title("Global Smartphones Brand Market Share") + ax.set_xlabel("Brand") + ax.set_ylabel("Market Share (%)") + ax.set_ylim(0, 35) + + for i, value in enumerate(market_share): + ax.text(i, value + 0.5, str(value) + "%", ha="center") + + fig.tight_layout() + + return fig # ============================================================ @@ -68,7 +108,26 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 - pass + df = _load_data() + + df = df[df["region"].isin(["North", "South"])].copy() + df["month"] = df["order_date"].dt.to_period("M").dt.to_timestamp() + + monthly = df.groupby(["month", "region"])["amount"].sum().reset_index() + + fig, ax = plt.subplots(figsize=(10, 5)) + + sns.lineplot(data=monthly, x="month", y="amount", hue="region", marker="o", ax=ax) + + ax.set_title("Monthly Revenue Trend: North vs South") + ax.set_xlabel("Month") + ax.set_ylabel("Total Revenue") + ax.legend(title="Region") + + fig.autofmt_xdate() + fig.tight_layout() + + return fig def yellow_box_vip(): @@ -78,7 +137,19 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 - pass + df = _load_data() + + fig, ax = plt.subplots(figsize=(8, 4)) + + sns.boxplot(data=df, x="vip_level", y="amount", ax=ax) + + ax.set_title("Order Amount Distribution by VIP Level") + ax.set_xlabel("VIP Level") + ax.set_ylabel("Order Amount") + + fig.tight_layout() + + return fig def yellow_scatter_price_amount(): @@ -88,7 +159,19 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 - pass + df = _load_data() + + fig, ax = plt.subplots(figsize=(8, 4)) + + sns.scatterplot(data=df, x="unit_price", y="amount", ax=ax) + + ax.set_title("Unit Price vs Order Amount") + ax.set_xlabel("Unit Price") + ax.set_ylabel("Order Amount") + + fig.tight_layout() + + return fig # ============================================================ @@ -107,4 +190,49 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 - pass + df = _load_data() + + sub = df[df["category"] == category].copy() + sub["month"] = sub["order_date"].dt.to_period("M").dt.to_timestamp() + + fig, ax = plt.subplots(2, 2, figsize=(14, 10)) + + # 1. 左上:月營收趨勢 (折線圖) + monthly = sub.groupby("month")["amount"].sum().reset_index() + + sns.lineplot(data=monthly, x="month", y="amount", ax=ax[0, 0]) + + ax[0, 0].set_title(f"{category} Monthly Revenue Trend") + ax[0, 0].set_xlabel("Month") + ax[0, 0].set_ylabel("Revenue") + + # 2. 右上:各地區營收 (長條圖) + region_sales = sub.groupby("region")["amount"].sum().reset_index() + + sns.barplot(data=region_sales, x="region", y="amount", ax=ax[0, 1]) + + ax[0, 1].set_title(f"{category} Revenue by Region") + ax[0, 1].set_xlabel("Region") + ax[0, 1].set_ylabel("Revenue") + + # 3. 左下:Top 5 商品營收 (水平長條圖) + top_products = sub.groupby("product_name")["amount"].sum().sort_values(ascending=False).head(5).reset_index() + + sns.barplot(data=top_products, x="amount", y="product_name", ax=ax[1, 0]) + + ax[1, 0].set_title(f"{category} Top 5 Products by Revenue") + ax[1, 0].set_xlabel("Revenue") + ax[1, 0].set_ylabel("Product Name") + + # 4. 右下:訂單金額分佈 (直方圖) + sns.histplot(data=sub, x="amount", bins=20, ax=ax[1, 1]) + + ax[1, 1].set_title(f"{category} Order Amount Distribution") + ax[1, 1].set_xlabel("Order Amount") + ax[1, 1].set_ylabel("Count") + + fig.suptitle(f"Category Dashboard: {category}", fontsize=16) + fig.autofmt_xdate() + fig.tight_layout() + + return fig \ No newline at end of file diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..c739686 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -26,7 +26,31 @@ def green_plotly_bar(): 提示:px.bar() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + + category_revenue = df.groupby("category")["amount"].sum().reset_index().sort_values("amount", ascending=False) + + fig = px.bar( + category_revenue, + x="category", + y="amount", + title="Revenue by Category", + labels={"category": "Category", "amount": "Total Revenue"}, + text="amount" + ) + + fig.update_traces( + texttemplate="%{text:.0f}", + textposition="outside" + ) + fig.update_layout( + xaxis_title="Category", + yaxis_title="Total Revenue", + uniformtext_minsize=8, + uniformtext_mode="hide" + ) + + return fig def green_plotly_line(): @@ -37,7 +61,27 @@ def green_plotly_line(): 提示:先 groupby 月份算總營收,再 px.line() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + df["order_date"] = pd.to_datetime(df["order_date"]) + df["month"] = df["order_date"].dt.to_period("M").dt.to_timestamp() + + monthly = df.groupby("month")["amount"].sum().reset_index().sort_values("month") + + fig = px.line( + monthly, + x="month", + y="amount", + markers=True, + title="Monthly Revenue Trend", + labels={"month": "Month", "amount": "Total Revenue"} + ) + + fig.update_layout( + xaxis_title="Month", + yaxis_title="Total Revenue" + ) + + return fig def green_plotly_pie(): @@ -48,7 +92,20 @@ def green_plotly_pie(): 提示:px.pie() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + + vip_counts = df["vip_level"].value_counts().reset_index() + vip_counts.columns = ["vip_level", "order_count"] + + fig = px.pie(vip_counts, names="vip_level", values="order_count", title="Order Count Share by VIP Level") + + fig.update_traces( + textinfo="label+percent", + hovertemplate="VIP Level: %{label}
Order Count: %{value}
Share: %{percent}" + ) + fig.update_layout(title_x=0.5) + + return fig # ============================================================ @@ -63,7 +120,27 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 回傳:合併後的 DataFrame """ # TODO: 你的程式碼 - pass + orders = pd.read_csv(raw_path) + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) + + # 欄位名稱 + orders.columns = orders.columns.str.strip().str.lower() + + # 金額 + orders["amount"] = (orders["amount"].astype(str).str.replace("$", "", regex=False).str.replace(",", "", regex=False).astype(float)) + + # 日期 + orders["order_date"] = pd.to_datetime(orders["order_date"], errors="coerce") + + # 缺值、去重 + orders = orders.dropna(subset=["amount", "order_date"]).drop_duplicates() + + # 合併 + merge = orders.merge(customers, on="customer_id", how="left") + merge = merge.merge(products, on="product_id", how="left") + + return merge def yellow_kpi_summary(df): @@ -77,7 +154,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 + + 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 +178,27 @@ 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="Unit Price vs Order Amount by Category", + labels={ + "unit_price": "Unit Price", + "amount": "Order Amount", + "category": "Category", + "product_name": "Product Name" + } + ) + + fig.update_layout( + xaxis_title="Unit Price", + yaxis_title="Order Amount" + ) + + return fig # ============================================================ @@ -115,4 +222,38 @@ 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", + ) + + df["month"] = df["order_date"].dt.to_period("M").dt.to_timestamp() + + monthly = df.groupby("month")["amount"].sum().reset_index().sort_values("month") + top_products = df.groupby("product_name")["amount"].sum().sort_values(ascending=False).head(10).reset_index() + region_revenue = df.groupby("region")["amount"].sum().reset_index().sort_values("amount", ascending=False) + category_revenue = df.groupby("category")["amount"].sum().reset_index().sort_values("amount", ascending=False) + + 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"}]] + ) + + fig.add_trace(go.Scatter(x=monthly["month"], y=monthly["amount"], mode="lines+markers", name="Monthly Revenue"), row=1, col=1) + fig.add_trace(go.Bar(x=top_products["product_name"], y=top_products["amount"], name="Top Products"), row=1, col=2) + fig.add_trace(go.Bar(x=region_revenue["region"], y=region_revenue["amount"], name="Region Revenue"), row=2, col=1) + fig.add_trace(go.Pie(labels=category_revenue["category"], values=category_revenue["amount"], hole=0.4, name="Category Revenue"), 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="Product Name", row=1, col=2) + fig.update_yaxes(title_text="Revenue", 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="Ecommerce Dashboard", title_x=0.5, height=800, showlegend=True) + + return fig \ No newline at end of file