From 226f3ed9a00e04c63e08b3fa4e186e634d35c445 Mon Sep 17 00:00:00 2001 From: he00298902 Date: Fri, 1 May 2026 13:15:18 +0800 Subject: [PATCH 1/2] =?UTF-8?q?WIP:=20=E6=9B=B4=E6=96=B0=E7=9B=AE=E5=89=8D?= =?UTF-8?q?=E4=BD=9C=E6=A5=AD=E9=80=B2=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 28 ++- homework/m2_pandas_cleaning.py | 34 +++- homework/pr.ipynb | 343 +++++++++++++++++++++++++++++++++ 3 files changed, 391 insertions(+), 14 deletions(-) create mode 100644 homework/pr.ipynb diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..6ae3073 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,19 +19,24 @@ 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]) + mask = arr > 25 + return arr[mask] # ============================================================ @@ -42,7 +47,8 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 - pass + mask = prices > 1000 + return int(mask.sum()) def yellow_top3_stock_indices(stocks): @@ -51,7 +57,8 @@ def yellow_top3_stock_indices(stocks): 提示:np.argsort """ # TODO: 你的程式碼 - pass + stocks2 = np.argsort(stocks)# 這部分說的是按照庫存排序 並返回的是 index值不是排序的數值 + return stocks2[::-1][:3] def yellow_restock_cost(prices, stocks): @@ -60,7 +67,11 @@ def yellow_restock_cost(prices, stocks): 提示:布林遮罩 + .sum() """ # TODO: 你的程式碼 - pass + prices_mask = prices < 500 + cheap_prices = prices[prices_mask] + cheap_prices_stocks = cheap_prices * 50 + total_cheap_prices_stocks = cheap_prices_stocks.sum() + return total_cheap_prices_stocks # ============================================================ @@ -77,4 +88,7 @@ def red_double11_prices(prices, stocks): 提示:np.where 可以巢狀使用 """ # TODO: 你的程式碼 - pass + prices_11 = np.where(stocks >= 100, prices * 0.7, + np.where((stocks >= 20) & (stocks <=99), prices*0.9, prices)) + return prices_11 + diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..796a644 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -20,7 +20,9 @@ def green_read_csv(): 提示:pd.read_csv() """ # TODO: 你的程式碼 - pass + ras = pd.read_csv('datasets/ecommerce/orders_raw.csv') + + return ras def green_shape(df): @@ -29,7 +31,7 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 - pass + return df.shape def green_dtypes(df): @@ -38,7 +40,7 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 - pass + return df.dtypes # ============================================================ @@ -52,7 +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,7 +67,11 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass + new_df = df.copy() + new_df['amount'] = new_df['amount'].astype(str).str.replace('$', '', regex=False) + new_df['amount'] = new_df['amount'].astype(str).str.replace(',', '', regex=False).astype(float) + + return new_df def yellow_drop_duplicates(df): @@ -72,7 +80,10 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass + new_df = df.copy() + new_df = new_df.drop_duplicates() + + return new_df # ============================================================ @@ -93,4 +104,13 @@ def red_clean_orders(path): 提示:pd.to_datetime(errors='coerce') """ # TODO: 你的程式碼 - pass + df = pd.read_csv(path) + new_df = df.copy() + new_df.columns = new_df.columns.str.strip().str.lower() + new_df['amount'] = new_df['amount'].astype(str).str.replace('$','',regex=False) + new_df['amount'] = new_df['amount'].astype(str).str.replace(',','',regex=False).astype(float) + new_df['order_date'] = pd.to_datetime(new_df['order_date'],errors='coerce') + new_df = new_df.dropna(subset= ['order_date']) + new_df = new_df.dropna(subset= ['amount']) + new_df = new_df.drop_duplicates() + return new_df diff --git a/homework/pr.ipynb b/homework/pr.ipynb new file mode 100644 index 0000000..e766e91 --- /dev/null +++ b/homework/pr.ipynb @@ -0,0 +1,343 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 16, + "id": "bf2ad3cc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "30.0\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "df = '../datasets/ecommerce/products.csv'\n", + "prices = np.genfromtxt(df, delimiter=',', skip_header=1,usecols=3)\n", + "stocks = np.genfromtxt(df, delimiter=',', skip_header=1, usecols=4)\n", + "\n", + "def green_mean():\n", + " \"\"\"建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)\"\"\"\n", + " # TODO: 你的程式碼\n", + " arr = np.array([10, 20, 30, 40, 50])\n", + " return arr.mean()\n", + "\n", + "\n", + "print(green_mean())" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "e88dbde8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 20 40 60 80 100]\n" + ] + } + ], + "source": [ + "def green_double():\n", + " \"\"\"建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray\"\"\"\n", + " # TODO: 你的程式碼\n", + " arr = np.array([10, 20, 30, 40, 50])\n", + " return arr * 2\n", + "\n", + "print(green_double())\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "1d7a4aa3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[30 40 50]\n" + ] + } + ], + "source": [ + "def green_filter():\n", + " \"\"\"建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)\"\"\"\n", + " # TODO: 你的程式碼\n", + " arr = np.array([10, 20, 30, 40, 50])\n", + " mask = arr > 25\n", + " return arr[mask]\n", + "\n", + "print(green_filter())" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "8244ee27", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "16\n" + ] + } + ], + "source": [ + "\n", + "def yellow_expensive_count(prices):\n", + " \"\"\"回傳單價 > 1000 的商品數量 (int)\"\"\"\n", + " # TODO: 你的程式碼\n", + " mask = prices > 1000\n", + " return int(mask.sum())\n", + "print(yellow_expensive_count(prices))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "13f306da", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[7 8 3]\n" + ] + } + ], + "source": [ + "def yellow_top3_stock_indices(stocks):\n", + " \"\"\"\n", + " 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排)\n", + " 提示:np.argsort\n", + " \"\"\"\n", + " # TODO: 你的程式碼\n", + " stocks2 = np.argsort(stocks)# 這部分說的是按照庫存排序 並返回的是 index值不是排序的數值 \n", + " return stocks2[::-1][:3]\n", + "\n", + "\n", + "print(yellow_top3_stock_indices(stocks))" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "31cc3fc9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "120300.0\n" + ] + } + ], + "source": [ + "def yellow_restock_cost(prices, stocks):\n", + " \"\"\"\n", + " 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int)\n", + " 提示:布林遮罩 + .sum()\n", + " \"\"\"\n", + " # TODO: 你的程式碼\n", + " prices_mask = prices < 500\n", + " cheap_prices = prices[prices_mask] \n", + " \n", + " cheap_prices_stocks = cheap_prices * 50\n", + "\n", + " total_cheap_prices_stocks = cheap_prices_stocks.sum()\n", + " \n", + " return total_cheap_prices_stocks\n", + " \n", + "print(yellow_restock_cost(prices, stocks))\n", + "\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dec484cc", + "metadata": {}, + "outputs": [], + "source": [ + "def red_double11_prices(prices, stocks):\n", + " \"\"\"\n", + " 雙 11 定價規則(必須向量化,不能用 for-loop):\n", + " - 庫存 >= 100:打 7 折\n", + " - 庫存 20~99:打 9 折\n", + " - 庫存 < 20:原價\n", + " 回傳每個商品的雙 11 售價 (ndarray)\n", + " 提示:np.where 可以巢狀使用\n", + " \"\"\"\n", + " # TODO: 你的程式碼\n", + " prices_11 = np.where(stocks >= 100, prices * 0.7, \n", + " np.where((stocks >= 20) & (stocks <=99), prices*0.9, prices))\n", + " return prices_11\n", + "\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "e23b7d0a", + "metadata": {}, + "outputs": [], + "source": [ + "DATA = '../datasets/ecommerce/orders_raw.csv'\n", + "\n", + "df = pd.read_csv(DATA)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "5584cbfb", + "metadata": {}, + "outputs": [], + "source": [ + "def green_read_csv():\n", + " \"\"\"\n", + " 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理)\n", + " 提示:pd.read_csv()\n", + " \"\"\"\n", + " # TODO: 你的程式碼\n", + " ras = pd.read_csv('datasets/ecommerce/orders_raw.csv')\n", + " \n", + " return ras" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad7a7634", + "metadata": {}, + "outputs": [], + "source": [ + "def green_dtypes(df):\n", + " \"\"\"\n", + " 回傳 DataFrame 的欄位型別 (Series)\n", + " 提示:df.dtypes\n", + " \"\"\"\n", + " # TODO: 你的程式碼\n", + " return df.dtypes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8474fcc6", + "metadata": {}, + "outputs": [], + "source": [ + "def yellow_clean_amount(df):\n", + " \"\"\"\n", + " 清理 amount 欄位:移除 '$' 和 ',' 符號,轉為 float\n", + " 假設欄位名稱已經是小寫的 'amount'\n", + " 回傳清理後的 DataFrame(不要修改原始 df)\n", + " 提示:.str.replace() + .astype(float)\n", + " \"\"\"\n", + " # TODO: 你的程式碼\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff0d5e96", + "metadata": {}, + "outputs": [], + "source": [ + "def yellow_clean_amount(df):\n", + " \"\"\"\n", + " 清理 amount 欄位:移除 '$' 和 ',' 符號,轉為 float\n", + " 假設欄位名稱已經是小寫的 'amount'\n", + " 回傳清理後的 DataFrame(不要修改原始 df)\n", + " 提示:.str.replace() + .astype(float)\n", + " \"\"\"\n", + " # TODO: 你的程式碼\n", + " new_df = df.copy()\n", + " new_df['amount'] = new_df['amount'].astype(str).str.replace('$', '', regex=False)\n", + " new_df['amount'] = new_df['amount'].astype(str).str.replace(',', '', regex=False).astype(float)\n", + "\n", + " return new_df" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "00161701", + "metadata": {}, + "outputs": [], + "source": [ + "def yellow_drop_duplicates(df):\n", + " \"\"\"\n", + " 移除完全重複的列,回傳去重後的 DataFrame\n", + " 提示:df.drop_duplicates()\n", + " \"\"\"\n", + " # TODO: 你的程式碼\n", + " new_df = df.copy()\n", + " new_df = new_df.drop_duplicates()\n", + " \n", + " return new_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69016fc7", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def red_clean_orders(path):\n", + " df = pd.read_csv(path)\n", + " new_df = df.copy()\n", + " new_df.columns = new_df.columns.str.strip().str.lower()\n", + " new_df['amount'] = new_df['amount'].astype(str).str.replace('$','',regex=False)\n", + " new_df['amount'] = new_df['amount'].astype(str).str.replace(',','',regex=False).astype(float)\n", + " new_df['order_date'] = pd.to_datetime(new_df['order_date'],errors='coerce')\n", + " new_df = new_df.dropna(subset= ['order_date'])\n", + " new_df = new_df.dropna(subset= ['amount'])\n", + " new_df = new_df.drop_duplicates()\n", + " return new_df" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 8a78e82f4fa1f6c42655738d2dcfd20a07971555 Mon Sep 17 00:00:00 2001 From: he00298902 Date: Mon, 4 May 2026 16:37:02 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m3_pandas_advanced.py | 37 +++++++-- homework/m4_timeseries.py | 41 +++++++--- homework/m5_visualization.py | 102 +++++++++++++++++++++-- homework/m6_plotly_capstone.py | 144 +++++++++++++++++++++++++++++++-- 4 files changed, 294 insertions(+), 30 deletions(-) diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..ca5bb21 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -24,19 +24,28 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - pass + orders =pd.read_csv('../datasets/ecommerce/orders_clean.csv',parse_dates=['order_date']) + customers =pd.read_csv('../datasets/ecommerce/customers.csv') + products =pd.read_csv('../datasets/ecommerce/products.csv') + + full = ( + orders + .merge(customers, on= 'customer_id', how= 'left') + .merge(products, on='product_id',how='left') + ) + return full 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 +59,9 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - pass + category_amount = df.groupby('category')['amount'].sum() + best_category_amount =category_amount.idxmax() + return best_category_amount def yellow_gold_vip_stats(df): @@ -60,7 +71,11 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - pass + gold_vip = df[df['vip_level'] == 'Gold'] + order_count = len(gold_vip) + total_amount = gold_vip['amount'].sum + + return(order_count, total_amount) def yellow_region_avg_amount(df): @@ -70,7 +85,8 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + region_mean = df.groupby('region')['amount'].mean() + return region_mean # ============================================================ @@ -94,4 +110,11 @@ 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')) + rfm = rfm.sort_values('M',ascending=False) + rfm = rfm.reset_index() + top5 = rfm.head() + return top5 diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..e4a3a7b 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -27,8 +27,13 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 - pass - + df = _load_data() + df['year'] = df['order_date'].dt.year + df['month']= df['order_date'].dt.month + df['weekday'] = df['order_date'].dt.day_name() + df['year_month'] = df['order_date'].dt.to_period('M') + month_avg_amount = df.groupby('month')['amount'].mean() + return month_avg_amount def green_top3_dates(): """ @@ -37,7 +42,9 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df = _load_data() + top3_dates = df['order_date'].value_counts().head(3) + return top3_dates def green_date_range(): @@ -46,7 +53,8 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 - pass + df = _load_data() + return (df['order_date'].min() ,df['order_date'].max()) # ============================================================ @@ -60,10 +68,15 @@ def yellow_monthly_revenue(): 提示:set_index('order_date').resample('ME')['amount'].sum() """ # TODO: 你的程式碼 - pass + df = _load_data() + ts = df.set_index('order_date') + month_sum_amount = ts.resample('ME')['amount'].sum() + + return month_sum_amount def yellow_rolling_avg(monthly_revenue): + """ 計算 3 個月移動平均 接收 yellow_monthly_revenue() 的結果作為輸入 @@ -71,8 +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 +93,9 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass + category_order_median = df.groupby('category')['amount'].median() + category_order_median = category_order_median.sort_values(ascending =False) + return category_order_median # ============================================================ @@ -101,4 +115,13 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + df = _load_data() + ts = df.set_index('order_date') + report = ts.resample('ME').agg( + order_count=('order_id', 'count'), + revenue=('amount', 'sum'), + active_customers=('customer_id', 'nunique'), + ) + report['avg_order_value'] = (report['revenue'] / report['order_count']).round(2) + report['revenue_growth'] = (report['revenue'].pct_change()*100.).round(2) + return report \ No newline at end of file diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..45e1980 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -29,7 +29,23 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ # TODO: 你的程式碼 - pass + df = _load_data() + order = df['category'].value_counts().index + + fig = plt.figure(figsize=(8, 4)) + sns.countplot( + data=df, + x='category', + order=order, + palette='viridis', + hue='category', + legend=False, +) + plt.title('Orders by Category') + plt.xlabel('Category') + plt.ylabel('Order Count') + plt.tight_layout() + return fig def green_hist_amount(): @@ -39,7 +55,14 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 - pass + df = _load_data() + fig = plt.figure(figsize=(8,4)) + sns.histplot(data= df, x= 'amount',bins=20) + plt.title('Distribution of Order Amount') + plt.xlabel('Amount') + plt.ylabel('Frequency') + plt.tight_layout() + return fig def green_set_labels(): @@ -51,7 +74,15 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass + df = _load_data() + fig = plt.figure(figsize=(8,4)) + region_rev = df.groupby('region')['amount'].sum().sort_values(ascending=False).reset_index() + sns.barplot(data=region_rev,x='region',y='amount',palette='viridis',hue='region',legend=True) + plt.title('Revenue by Region') + plt.xlabel('Region') + plt.ylabel('amount') + plt.tight_layout() + return fig # ============================================================ @@ -68,7 +99,19 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 - pass + df = _load_data() + df_ns = df[df['region'].isin(['North','South'])].copy() + df_ns['month'] = df_ns['order_date'].dt.to_period('M').dt.to_timestamp() + monthly = df_ns.groupby(['month','region'])['amount'].sum().reset_index() + + fig = plt.figure(figsize=(8,4)) + sns.lineplot(data = monthly, x='month',y='amount',hue='region',marker='o') + plt.title('Monthly Revenue Trend: North vs South') + plt.xlabel('Month') + plt.ylabel('Revenue') + plt.xticks(rotation=45) + plt.tight_layout() + return fig def yellow_box_vip(): @@ -78,7 +121,14 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 - pass + df = _load_data() + fig = plt.figure(figsize=(8,4)) + sns.boxplot(data=df,x= 'vip_level',y= 'amount',order=['Bronze','Silver','Gold','Platinum'],palette='viridis',hue='vip_level',legend=False) + plt.title('Order Amount Distribution by VIP Level') + plt.xlabel('VIP Level') + plt.ylabel('Amount') + plt.tight_layout() + return fig def yellow_scatter_price_amount(): @@ -88,7 +138,14 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 - pass + df = _load_data() + fig = plt.figure(figsize=(8, 5)) + sns.scatterplot(data=df, x='unit_price', y='amount',hue='category',alpha = 0.5) + plt.title('Unit Price vs Order Amount') + plt.xlabel('Unit Price') + plt.ylabel('Amount') + plt.tight_layout() + return fig # ============================================================ @@ -107,4 +164,35 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 - pass + df = _load_data() + df_cat = df[df['category'] == category].copy() + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + + df_cat['month'] = df_cat['order_date'].dt.to_period('M').dt.to_timestamp() + monthly = df_cat.groupby('month')['amount'].sum().reset_index() + sns.lineplot(data=monthly, x='month', y='amount', marker='o', ax=axes[0, 0]) + axes[0, 0].set_title(f'{category} Monthly Revenue Trend') + axes[0, 0].set_xlabel('Month') + axes[0, 0].set_ylabel('Revenue') + axes[0, 0].tick_params(axis='x', rotation=45) + + region_rev = df_cat.groupby('region')['amount'].sum().sort_values(ascending=False).reset_index() + sns.barplot(data=region_rev,x='region',y='amount',palette='viridis',hue='region',legend=False,ax=axes[0,1]) + axes[0, 1].set_title(f'{category} Revenue by Region') + axes[0, 1].set_xlabel('Region') + axes[0, 1].set_ylabel('Revenue') + + top5 = df_cat.groupby('product_name')['amount'].sum().nlargest(5).reset_index() + sns.barplot(data=top5,x='amount',y= 'product_name',palette='coolwarm',hue='product_name',legend=False,ax=axes[1,0]) + axes[1, 0].set_title(f'{category} Top 5 Products') + axes[1, 0].set_xlabel('Revenue') + axes[1, 0].invert_yaxis() + + sns.histplot(data=df_cat, x='amount', bins=20, kde=True, ax=axes[1, 1]) + axes[1, 1].set_title(f'{category} Order Amount Distribution') + axes[1, 1].set_xlabel('Amount') + axes[1, 1].set_ylabel('Frequency') + + fig.suptitle(f'Dashboard: {category}', fontsize=16, fontweight='bold') + plt.tight_layout() + return fig diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..ac556b3 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -26,7 +26,15 @@ def green_plotly_bar(): 提示:px.bar() """ # TODO: 你的程式碼 - pass + df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') + cat_rev =df.groupby('category')['amount'].sum().sort_values(ascending=False).reset_index() + fig = px.bar( + cat_rev, + x='category', + y='amount', + title='Revenue by Category', + labels={'category':'Category','amount':'Revenue'}) + return fig def green_plotly_line(): @@ -37,7 +45,18 @@ 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').dt.to_timestamp() + monthly = df.groupby('month')['amount'].sum().reset_index() + fig = px.line( + monthly, + x='month', + y='amount', + title='Monthly Revenue Trend', + labels={'month': 'Month', 'amount': 'Revenue'}, + markers=True + ) + return fig def green_plotly_pie(): @@ -48,7 +67,16 @@ 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() + fig = px.pie( + vip_counts, + names='vip_level', + values='count', + title='Orders by VIP Level' + ) + fig.update_traces(textposition='inside', textinfo='percent+label') + return fig # ============================================================ @@ -63,7 +91,26 @@ 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'].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() + + df = df.drop_duplicates() + + customers = pd.read_csv(customers_path,parse_dates=['signup_date']) + products = pd.read_csv(products_path) + + df = df.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 +124,16 @@ 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': total_revenue, + 'order_count': order_count, + 'active_customers': active_customers, + 'avg_order_value': avg_order_value, + } def yellow_plotly_scatter(df): @@ -91,7 +147,17 @@ 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'], + opacity= 0.6, + trendline='ols', + title='Unit Price vs Order Amount', + labels={'unit_price':'Unit Price','amount':'Amount'}) + return fig # ============================================================ @@ -115,4 +181,68 @@ 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() + fig = make_subplots( + rows= 2, cols= 2, + specs=[[{'type':'xy'},{'type':'xy'}],[{'type':'xy'},{'type':'pie'}]], + subplot_titles=( + 'Monthly Revenue Trend', + 'Top 10 Products', + 'Revenue by Region', + 'Revenue by Category', + )) + + monthly = df.groupby('month')['amount'].sum().reset_index() + fig.add_trace(go.Scatter( + x=monthly['month'], + y=monthly['amount'], + mode='lines+markers', + name='Revenue'), + row=1, + col=1) + + top10 = df.groupby('product_name')['amount'].sum().nlargest(10).reset_index() + fig.add_trace( + go.Bar( + x=top10['product_name'], + y=top10['amount'], + name='Top 10'), + row = 1, + col = 2 + ) + + region_rev =df.groupby('region')['amount'].sum().sort_values(ascending=False).reset_index() + fig.add_trace( + go.Bar( + x=region_rev['region'], + y=region_rev['amount'], + name='Region' + ), + 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'], + name='Category' + ), + row=2, + col=2 + ) + + fig.update_layout( + title_text='E-commerce Dashboard', + height= 800, + showlegend=False + ) + return fig \ No newline at end of file