diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..389f9a6 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -20,20 +20,22 @@ 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 陣列 @@ -43,6 +45,7 @@ def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 pass + return int((prices > 1000).sum()) def yellow_top3_stock_indices(stocks): @@ -52,7 +55,7 @@ def yellow_top3_stock_indices(stocks): """ # TODO: 你的程式碼 pass - + return np.argsort(stocks)[::-1][:3] def yellow_restock_cost(prices, stocks): """ @@ -61,20 +64,15 @@ def yellow_restock_cost(prices, stocks): """ # TODO: 你的程式碼 pass - - + return (prices[prices < 500] * 50).sum() # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ def red_double11_prices(prices, stocks): - """ - 雙 11 定價規則(必須向量化,不能用 for-loop): - - 庫存 >= 100:打 7 折 - - 庫存 20~99:打 9 折 - - 庫存 < 20:原價 - 回傳每個商品的雙 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..39ca3d9 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 + df = pd.read_csv('../datasets/ecommerce/orders_raw.csv') + return df + 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,8 @@ 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 + df_new = df.copy() + df_new.columns = df_new.columns.str.strip().str.lower() + return df_new def yellow_clean_amount(df): @@ -63,7 +68,16 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass + df_new = df.copy() + df_new['amount'] = ( + df_new['amount'] + .astype(str) + .str.replace('$', '') + .str.replace(',', '') + .astype(float) + ) + + return df_new def yellow_drop_duplicates(df): @@ -72,7 +86,9 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass + df_new = df.copy() + df_new = df_new.drop_duplicates() + return df_new # ============================================================ @@ -93,4 +109,34 @@ 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'] + .astype(str) + .str.replace('$', '') + .str.replace(',', '') + .astype(float) + ) + + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + + df = df.dropna(subset=['amount']) + + df = df.dropna(subset=['order_date']) + + df = df.drop_duplicates() + + return df + + + + + + + + + + diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..e181d8b 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -24,19 +24,30 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + + 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 + return df.shape[0] def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" # TODO: 你的程式碼 - pass + return list(df.columns) # ============================================================ @@ -50,7 +61,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 +71,12 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - pass + df_gold = df.groupby('vip_level').agg( + 總訂單數 = ('order_id', 'count'), + 總金額 = ('amount', 'sum') + ).loc['Gold'] + + return f'(訂單數 {df_gold.values[0].astype(int)}, 總金額 {df_gold.values[1]})' def yellow_region_avg_amount(df): @@ -70,7 +86,8 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + region_info = df.groupby('region')['amount'].mean() + return region_info # ============================================================ @@ -94,4 +111,10 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + rfm = df.groupby('customer_id').agg( + R = ('order_date', 'max'), + F = ('order_id', 'count'), + M = ('amount', 'sum') + ).reset_index().sort_value('M', ascending = False).head() + + return rfm diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..fe5dbcc 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -27,7 +27,11 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 - pass + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + df['month'] = df['order_date'].dt.month + df_mon = df.groupby('month')['amount'].mean() + return df_mon def green_top3_dates(): @@ -37,7 +41,11 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + ts = df.set_index('order_date').sort_index() + top3 = ts['order_id'].resample('D').count().head(3) + return top3 def green_date_range(): @@ -46,7 +54,10 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 - pass + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + + return f'({list(df['order_date'].sort_values())[0]}, {list(df['order_date'].sort_values())[-1]})' # ============================================================ @@ -60,7 +71,14 @@ def yellow_monthly_revenue(): 提示:set_index('order_date').resample('ME')['amount'].sum() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + ts = df.set_index('order_date').sort_index() + + mon_rev = ts.resample('ME')['amount'].sum() + + return mon_rev + def yellow_rolling_avg(monthly_revenue): @@ -71,7 +89,17 @@ def yellow_rolling_avg(monthly_revenue): 提示:.rolling(window=3).mean() """ # TODO: 你的程式碼 - pass + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + ts = df.set_index('order_date').sort_index() + + mon_rev = ts.resample('ME')['amount'].sum() + + mon_roll = mon_rev.rolling(window = monthly_revenue).mean() + + return mon_roll + + def yellow_category_median(df): @@ -81,7 +109,8 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass + cate_med = df.groupby('category')['amount'].median().sort_values(ascending = False) + return cate_med # ============================================================ @@ -101,4 +130,27 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) + ts = df.set_index('order_date').sort_index() + + mon_count = ts['order_id'].resample('ME').count() + + mon_rev = ts['amount'].resample('ME').sum() + + mon_act = ts['customer_id'].resample('ME').nunique() + + mon_avg = mon_rev / mon_count + + mon_rev_grow = mon_rev.pct_change() + + mon_info = pd.DataFrame({ + 'order_count': mon_count, + 'revenue': mon_rev, + 'active_customers': mon_act, + 'avg_order_value': mon_avg, + 'revenue_growth': mon_rev_grow, + }) + + return mon_info + diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..b8c53d3 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 = pd.read_csv( + '../datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + + category = df.groupby('category')['order_id'].count().reset_index() + plt.figure(figsize=(10, 4)) + sns.barplot(data=category, x='category', y='order_id', palette='viridis', hue='category', legend=False) + plt.title('category_count', fontweight='bold') + plt.xlabel('category') + plt.ylabel('order_count') + plt.tight_layout() + plt.show() + def green_hist_amount(): @@ -39,7 +51,14 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 - pass + df = pd.read_csv( + '../datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + sns.histplot(data=df, x='amount', bins=20, kde=True) + plt.title('amount Distribution') + plt.show() + + def green_set_labels(): @@ -51,7 +70,19 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass + df = pd.read_csv( + '../datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + + category = df.groupby('category')['order_id'].count().reset_index() + plt.figure(figsize=(10, 4)) + sns.barplot(data=category, x='category', y='order_id', palette='viridis', hue='category', legend=False) + plt.title('category_count', fontweight='bold') + plt.xlabel('category') + plt.ylabel('order_count') + plt.tight_layout() + plt.show() + # ============================================================ @@ -68,7 +99,25 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 - pass + df = pd.read_csv( + '../datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + df['month'] = df['order_date'].dt.to_period('M').astype(str) + + monthly_date = df.groupby(['month', 'region'])['amount'].sum() + + target_region = monthly_date[monthly_date['region'].isin(['North', 'South'])] + + fig, ax = plt.subplots(figsize=(10, 6)) + + sns.lineplot(data= target_region, x='month', y='amount', + hue='region', marker='o', ax=ax) + + ax.set_title('Monthly Revenue: North vs South', fontweight='bold') + ax.set_xlabel('Month') + ax.set_ylabel('Total Revenue') + plt.xticks(rotation=45) + def yellow_box_vip(): @@ -78,7 +127,19 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 - pass + df = pd.read_csv( + '../datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + + plt.figure(figsize=(9, 5)) + sns.boxplot(data=df, x='vip_level', y='amount', palette='Set2', hue='vip_level', legend=False) + plt.title('Order Amount Distribution by vip_level', fontweight='bold') + plt.xlabel('vip_level') + plt.ylabel('Amount') + plt.xticks(rotation=15) + plt.tight_layout() + plt.show() + def yellow_scatter_price_amount(): @@ -88,7 +149,21 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 - pass + df = pd.read_csv( + '../datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + + + plt.figure(figsize=(10, 6)) + sns.scatterplot(data=df, x='unit_price', y='amount', + hue='category', alpha=0.6, s=60) + plt.title('Unit Price vs Order Amount (by Category)', fontweight='bold') + plt.xlabel('Unit Price') + plt.ylabel('Order Amount') + plt.legend(bbox_to_anchor=(1.02, 1), loc='upper left') + plt.tight_layout() + plt.show() + # ============================================================