diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..1f19b2c 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -18,40 +18,55 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" - # TODO: 你的程式碼 - pass + # TODO: + arr = np.array([10, 20, 30, 40, 50]) + return arr.mean() +print(green_mean()) def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" - # TODO: 你的程式碼 - pass + # TODO: + arr = np.array([10, 20, 30, 40, 50]) + return arr * 2 +print(green_double()) def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" - # TODO: 你的程式碼 - pass + # TODO: + arr = np.array([10, 20, 30, 40, 50]) + return arr[arr > 25] +print(green_filter()) # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # 以下函式會接收從 products.csv 讀出的 prices, stocks 陣列 # ============================================================ +DATA = 'datasets/ecommerce/products.csv' +prices = np.genfromtxt(DATA, delimiter=',', skip_header=1 , usecols=3) +stocks = np.genfromtxt(DATA,delimiter=',', skip_header=1, usecols=4) def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" - # TODO: 你的程式碼 - pass + # TODO: + return len(prices[prices > 1000]) +print(yellow_expensive_count(prices)) def yellow_top3_stock_indices(stocks): """ 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) 提示:np.argsort """ - # TODO: 你的程式碼 - pass + # TODO: + idx = np.argsort(stocks) + top3_stocks = idx [-3:][::-1] + return top3_stocks + +print(yellow_top3_stock_indices(stocks)) + def yellow_restock_cost(prices, stocks): @@ -59,9 +74,13 @@ def yellow_restock_cost(prices, stocks): 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) 提示:布林遮罩 + .sum() """ - # TODO: 你的程式碼 - pass + # TODO: + mask = prices < 500 + price_500 = prices[mask] + total_500 = (price_500 * 50).sum() + return total_500 +print(yellow_restock_cost(prices, stocks)) # ============================================================ # 🔴 挑戰題(25 分) @@ -76,5 +95,14 @@ def red_double11_prices(prices, stocks): 回傳每個商品的雙 11 售價 (ndarray) 提示:np.where 可以巢狀使用 """ - # TODO: 你的程式碼 - pass + # TODO: + prices_07 = prices[stocks >= 100]* 0.7 + prices_09 = prices[(stocks >= 20) & (stocks <= 99)] * 0.9 + prices_original = prices[stocks < 20] + + finall_price = np.concatenate([prices_07, prices_09, prices_original]) + + #運用np.where取代if-else的方式 + finall_price2 = np.where(stocks >= 100, prices * 0.7,#庫存大於100 ,true:價格x0.7 + np.where(stocks >= 20,prices * 0.9 ,prices))#庫存 >= 20 ,true:價格x0.9,庫存 < 20,false:價格原價 + return finall_price2 \ No newline at end of file diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..6d9a375 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -8,8 +8,7 @@ 資料路徑:datasets/ecommerce/orders_raw.csv """ import pandas as pd - - +df = pd.read_csv('datasets/ecommerce/orders_raw.csv') # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ @@ -19,8 +18,10 @@ def green_read_csv(): 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) 提示:pd.read_csv() """ - # TODO: 你的程式碼 - pass + # TODO: + DATA ='datasets/ecommerce/orders_raw.csv' + df = pd.read_csv(DATA) + return df def green_shape(df): @@ -28,8 +29,9 @@ def green_shape(df): 回傳 DataFrame 的 (列數, 欄數) tuple 提示:df.shape """ - # TODO: 你的程式碼 - pass + # TODO: + return df.shape + def green_dtypes(df): @@ -37,8 +39,8 @@ def green_dtypes(df): 回傳 DataFrame 的欄位型別 (Series) 提示:df.dtypes """ - # TODO: 你的程式碼 - pass + # TODO: + return df.dtypes # ============================================================ @@ -51,8 +53,11 @@ def yellow_clean_columns(df): 回傳清理後的 DataFrame(不要修改原始 df) 提示:df.columns.str.strip().str.lower() """ - # TODO: 你的程式碼 - pass + # TODO: + new_df = df.copy() + new_df = new_df.columns.str.strip().str.lower() + return new_df + def yellow_clean_amount(df): @@ -62,8 +67,17 @@ def yellow_clean_amount(df): 回傳清理後的 DataFrame(不要修改原始 df) 提示:.str.replace() + .astype(float) """ - # TODO: 你的程式碼 - pass + # TODO: + new_df = df.copy() + + new_df['amount'] = ( + new_df['amount'] + .astype(str) + .str.replace('$' ,'', regex=False) + .str.replace(',' ,'', regex=False) + .astype(float) + ) + return new_df def yellow_drop_duplicates(df): @@ -71,8 +85,8 @@ def yellow_drop_duplicates(df): 移除完全重複的列,回傳去重後的 DataFrame 提示:df.drop_duplicates() """ - # TODO: 你的程式碼 - pass + # TODO: + return df.drop_duplicates() # ============================================================ @@ -92,5 +106,26 @@ def red_clean_orders(path): 回傳:清理後的 DataFrame 提示:pd.to_datetime(errors='coerce') """ - # TODO: 你的程式碼 - pass + # TODO: + df = pd.read_csv(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(subset=['amount','order_date']) + + df = df.drop_duplicates() + + return df + +my_path = 'datasets/ecommerce/orders_raw.csv' +print(red_clean_orders(my_path)) \ No newline at end of file diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..e619109 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -10,7 +10,11 @@ - datasets/ecommerce/products.csv """ import pandas as pd +import numpy as np +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') # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) @@ -23,20 +27,22 @@ def green_load_and_merge(): - 再 LEFT JOIN products.csv ON product_id 提示:pd.merge(how='left') """ - # TODO: 你的程式碼 - pass - - + # TODO: + df = (orders.merge(customers , on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + return df + def green_row_count(df): """回傳 DataFrame 的列數 (int)""" - # TODO: 你的程式碼 - pass + # TODO: + return len(df) def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" - # TODO: 你的程式碼 - pass + # TODO: + return list(df.columns) # ============================================================ @@ -49,8 +55,9 @@ def yellow_top_category(df): 回傳該類別名稱 (str) 提示:groupby('category')['amount'].sum() """ - # TODO: 你的程式碼 - pass + # TODO: + category = df.groupby('category')['amount'].sum().sort_values(ascending=False) + return category.index[0] def yellow_gold_vip_stats(df): @@ -59,8 +66,18 @@ def yellow_gold_vip_stats(df): 回傳 tuple: (訂單數 int, 總金額 float) 提示:df[df['vip_level'] == 'Gold'] """ - # TODO: 你的程式碼 - pass + # TODO: + # Gold_df = df[df["vip_level"] == "Gold"] + Gold_amount = int(df[df["vip_level"] == "Gold"]["amount"].count()) + Gold_total = float(df[df["vip_level"] == "Gold"]["amount"].sum()) + + # df = pd.DataFrame({ + # "VIP":['Gold'], + # "訂單數":[Gold_amount], + # "總金額":[Gold_total] + # # }) + + return (Gold_amount,Gold_total) def yellow_region_avg_amount(df): @@ -69,9 +86,10 @@ def yellow_region_avg_amount(df): 回傳 Series(index=region, values=平均金額) 提示:groupby('region')['amount'].mean() """ - # TODO: 你的程式碼 - pass - + # TODO: + df_mean = df.groupby("region")["amount"].mean().sort_values(ascending=False) + print(df_mean) + return df_mean # ============================================================ # 🔴 挑戰題(25 分) @@ -93,5 +111,25 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ - # TODO: 你的程式碼 - pass + # TODO: + rfm = df.groupby('customer_id').agg( + R=('order_date', 'max'), + F=('order_id', 'count'), + M=('amount', 'sum'), + ).reset_index() #計算 RFM過程. + + name_map = df[['customer_id', 'customer_name']].drop_duplicates() #重複的customer_id/customer_name去除. + rfm_named = rfm.merge(name_map, on='customer_id', how='left') #把整理好的customer_id/customer_name合併. + + top5 = ( + rfm_named + .sort_values('M', ascending=False) + .head(5) + .reset_index(drop=True) + [['customer_id', 'customer_name', 'R', 'F', 'M']] + ) #按M由大到小排序,取前5筆 + + return top5 + + + diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..e48fad6 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -7,6 +7,7 @@ 資料路徑:datasets/ecommerce/orders_enriched.csv """ import pandas as pd +import numpy as np def _load_data(): @@ -20,48 +21,56 @@ def _load_data(): # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -def green_avg_by_month(): +def green_avg_by_month(df): """ 計算每個月份 (1~12) 的平均訂單金額 回傳 Series(index=月份 1~12, values=平均金額) 提示:df['order_date'].dt.month """ - # TODO: 你的程式碼 - pass + # TODO: + # df = _load_data() #用df去替代讀取 + return df.groupby(df['order_date'].dt.month)['amount'].mean() #使用dt.month抓出1, 2, 3月... -def green_top3_dates(): +def green_top3_dates(df): """ 找出訂單數最多的前 3 個日期 回傳 Series(index=日期, values=訂單數, 由多到少排序) 提示:value_counts().head(3) """ - # TODO: 你的程式碼 - pass - - -def green_date_range(): + # TODO: + # ts = df.set_index('order_date').sort_index() + # ts['order_id'].resample('D').count().sort_values(ascending=False).head(3) + # return ts + # df = _load_data() 用df去替代讀取 + top3_dates =df['order_date'].value_counts().head(3) + return top3_dates + +def green_date_range(df): """ 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) 格式為 pandas Timestamp """ - # TODO: 你的程式碼 - pass - + # TODO: + # df = _load_data() 用df去替代讀取 + start_date = df['order_date'].min() #最早日期 + finally_date = df['order_date'].max() #最晚日期 + return(start_date, finally_date) # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ -def yellow_monthly_revenue(): +def yellow_monthly_revenue(df): """ 計算每月總營收 回傳 Series(index=月底日期 period, values=總營收) 提示:set_index('order_date').resample('ME')['amount'].sum() """ - # TODO: 你的程式碼 - pass - + # TODO: + # df = _load_data() 用df去替代讀取 + ts = df.set_index('order_date').sort_index() + return ts['amount'].resample('ME').sum() def yellow_rolling_avg(monthly_revenue): """ @@ -70,8 +79,10 @@ def yellow_rolling_avg(monthly_revenue): 回傳 Series(同樣 index,values=移動平均,前 2 筆可為 NaN) 提示:.rolling(window=3).mean() """ - # TODO: 你的程式碼 - pass + # TODO: + # monthly_revenue = df.set_index('order_date').sort_index() + # monthly_revenue['order_id'].resample('ME').count().rolling(window=3).mean().head(15) + return monthly_revenue.rolling(window=3).mean() def yellow_category_median(df): @@ -80,15 +91,21 @@ def yellow_category_median(df): 回傳 Series(index=category, values=中位數) 提示:groupby + median + sort_values """ - # TODO: 你的程式碼 - pass + # TODO: + category_median = ( + df.groupby('category')['amount'] + .median() + .sort_values(ascending=False) ##.sort_values() Pandas對數值進行排序,ascending=False是數值由大到小排序. + .round(1) + ) + return category_median # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ -def red_monthly_report(): +def red_monthly_report(df): """ 產出月報 DataFrame,每月一列,包含: - order_count:當月訂單數 @@ -100,5 +117,16 @@ def red_monthly_report(): index 為月份 (period 或 datetime) 提示:resample + agg + pct_change """ - # TODO: 你的程式碼 - pass + # TODO: + ts = df.set_index('order_date').sort_index() + + 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'] + 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..8a0b281 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -28,8 +28,15 @@ def green_bar_category(): 回傳 matplotlib Figure 物件 提示:sns.countplot 或 value_counts().plot.bar() """ - # TODO: 你的程式碼 - pass + # TODO: + df = _load_data() + fig, ax = plt.subplots() + + sns.countplot(data=df, x='category', ax=ax) + ax.set_title("Order Count by Category") + ax.tick_params(axis='x', rotation=45) + + return fig def green_hist_amount(): @@ -38,8 +45,14 @@ def green_hist_amount(): 回傳 matplotlib Figure 物件 提示:sns.histplot(bins=20) 或 plt.hist() """ - # TODO: 你的程式碼 - pass + # TODO: + df = _load_data() + fig, ax = plt.subplots() + + sns.histplot(df['amount'], bins=20, ax=ax) + ax.set_title("Amount Distribution") + + return fig def green_set_labels(): @@ -50,8 +63,18 @@ def green_set_labels(): - Y 軸標籤 (ylabel) 回傳 matplotlib Figure 物件 """ - # TODO: 你的程式碼 - pass + # TODO: + fig, ax = plt.subplots() + + x = ['A', 'B', 'C'] + y = [10, 20, 15] + + ax.bar(x, y) + ax.set_title("Sample Bar Chart") + ax.set_xlabel("Category") + ax.set_ylabel("Value") + + return fig # ============================================================ @@ -67,8 +90,22 @@ def yellow_line_region_trend(): 回傳 matplotlib Figure 物件 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ - # TODO: 你的程式碼 - pass + # TODO: + df = df[df['region'].isin(['North', 'South'])] + + df['month'] = df['order_date'].dt.to_period('M').dt.to_timestamp() + + grouped = df.groupby(['month', 'region'])['amount'].sum().reset_index() + + fig, ax = plt.subplots() + + sns.lineplot(data=grouped, x='month', y='amount', hue='region', ax=ax) + + ax.set_title("Monthly Revenue Trend (North vs South)") + ax.set_xlabel("Month") + ax.set_ylabel("Revenue") + + return fig def yellow_box_vip(): @@ -77,9 +114,15 @@ def yellow_box_vip(): 回傳 matplotlib Figure 物件 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ - # TODO: 你的程式碼 - pass + # TODO: + df = _load_data() + fig, ax = plt.subplots() + + sns.boxplot(data=df, x='vip_level', y='amount', ax=ax) + + ax.set_title("Amount Distribution by VIP Level") + return fig def yellow_scatter_price_amount(): """ @@ -87,8 +130,16 @@ def yellow_scatter_price_amount(): 回傳 matplotlib Figure 物件 提示:plt.scatter() 或 sns.scatterplot() """ - # TODO: 你的程式碼 - pass + # TODO: + df = _load_data() + fig, ax = plt.subplots() + + sns.scatterplot(data=df, x='unit_price', y='amount', ax=ax) + + ax.set_title("Unit Price vs Amount") + + return fig + # ============================================================ @@ -106,5 +157,47 @@ def red_category_dashboard(category="Electronics"): 回傳 matplotlib Figure 物件 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ - # TODO: 你的程式碼 - pass + # TODO: + df = _load_data() + + df = df[df['category'] == category] + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + + # 1️⃣ 月營收趨勢 + df['month'] = df['order_date'].dt.to_period('M').dt.to_timestamp() + trend = df.groupby('month')['amount'].sum().reset_index() + + sns.lineplot(data=trend, x='month', y='amount', ax=axes[0, 0]) + axes[0, 0].set_title("Monthly Revenue Trend") + + # 2️⃣ 各地區營收 + region_rev = df.groupby('region')['amount'].sum().reset_index() + + sns.barplot(data=region_rev, x='region', y='amount', ax=axes[0, 1]) + axes[0, 1].set_title("Revenue by Region") + + # 3️⃣ Top 5 商品 + top_products = ( + df.groupby('product_name')['amount'] + .sum() + .sort_values(ascending=False) + .head(5) + .reset_index() + ) + + sns.barplot( + data=top_products, + y='product_name', + x='amount', + ax=axes[1, 0] + ) + axes[1, 0].set_title("Top 5 Products") + + # 4️⃣ 金額分佈 + sns.histplot(df['amount'], bins=20, ax=axes[1, 1]) + axes[1, 1].set_title("Amount Distribution") + + plt.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..a612e18 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -25,8 +25,15 @@ def green_plotly_bar(): 回傳 plotly Figure 物件 提示:px.bar() """ - # TODO: 你的程式碼 - pass + # TODO: + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + + grouped = df.groupby("category")["amount"].sum().reset_index() + + fig = px.bar(grouped, x="category", y="amount", title="Revenue by Category") + + return fig + def green_plotly_line(): @@ -36,8 +43,17 @@ def green_plotly_line(): 回傳 plotly Figure 物件 提示:先 groupby 月份算總營收,再 px.line() """ - # TODO: 你的程式碼 - pass + # TODO: + 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() + + grouped = df.groupby("month")["amount"].sum().reset_index() + + fig = px.line(grouped, x="month", y="amount", + title="Monthly Revenue Trend") + + return fig def green_plotly_pie(): @@ -47,8 +63,16 @@ def green_plotly_pie(): 回傳 plotly Figure 物件 提示:px.pie() """ - # TODO: 你的程式碼 - pass + # TODO: + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + + grouped = df["vip_level"].value_counts().reset_index() + grouped.columns = ["vip_level", "count"] + + fig = px.pie(grouped, names="vip_level", values="count", + title="VIP Level Distribution") + + return fig # ============================================================ @@ -62,8 +86,35 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 2. 合併 customers.csv 和 products.csv 回傳:合併後的 DataFrame """ - # TODO: 你的程式碼 - pass + # TODO: + df = pd.read_csv(raw_path) + + # 欄位清理 + df.columns = df.columns.str.strip().str.lower() + + # amount 清理 + df["amount"] = ( + df["amount"] + .astype(str) + .str.replace("$", "", regex=False) + .str.replace(",", "", regex=False) + .astype(float) + ) + # 日期轉換 (這是 Capstone 的關鍵) + df["order_date"] = pd.to_datetime(df["order_date"]) + + # 去重與缺值 (假設刪除 amount 缺失的列) + df = df.drop_duplicates().dropna(subset=["amount"]) + + # 合併三張表 + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) + + # 注意:這裡要確保 id 欄位名稱一致,或使用 left_on/right_on + 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): @@ -76,9 +127,18 @@ def yellow_kpi_summary(df): "avg_order_value": float, # 平均客單價 } """ - # TODO: 你的程式碼 - pass - + # TODO: + total_revenue = df["amount"].sum() + order_count = df["order_id"].count() + 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): """ @@ -90,8 +150,17 @@ def yellow_plotly_scatter(df): 回傳 plotly Figure 物件 提示:px.scatter(hover_data=['product_name']) """ - # TODO: 你的程式碼 - pass + # TODO: + fig = px.scatter( + df, + x="unit_price", + y="amount", + color="category", + hover_data=["product_name"], + title="Unit Price vs Amount" + ) + + return fig # ============================================================ @@ -114,5 +183,70 @@ def red_dashboard(): 回傳 plotly Figure 物件 提示:from plotly.subplots import make_subplots """ - # TODO: 你的程式碼 - pass + # TODO: + 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() + + # Top 商品 + top_products = ( + df.groupby("product_name")["amount"] + .sum() + .sort_values(ascending=False) + .head(10) + .reset_index() + ) + + # 地區營收 + region_rev = df.groupby("region")["amount"].sum().reset_index() + + # 類別營收 + category_rev = df.groupby("category")["amount"].sum().reset_index() + + fig = make_subplots( + rows=2, cols=2, + subplot_titles=( + "Monthly Revenue", + "Top 10 Products", + "Revenue by Region", + "Category Share" + ), + specs=[[{}, {}], [{}, {"type": "domain"}]] + ) + + # 1️⃣ 月營收 + fig.add_trace( + go.Scatter(x=monthly["month"], y=monthly["amount"], mode="lines"), + row=1, col=1 + ) + + # 2️⃣ Top 商品 + fig.add_trace( + go.Bar(x=top_products["product_name"], y=top_products["amount"]), + row=1, col=2 + ) + + # 3️⃣ 地區 + fig.add_trace( + go.Bar(x=region_rev["region"], y=region_rev["amount"]), + row=2, col=1 + ) + + # 4️⃣ 類別 pie + fig.add_trace( + go.Pie(labels=category_rev["category"], + values=category_rev["amount"], + hole=0.4), + row=2, col=2 + ) + + fig.update_layout(title="E-commerce Dashboard", height=800) + + return fig \ No newline at end of file