# pandas [![RogelioKG/pandas](https://img.shields.io/badge/Sync%20with%20HackMD-grey?logo=markdown)](https://hackmd.io/@RogelioKG/pandas) ## 參考 + 🔗 [**JOIN 可視化**](https://joins.spathon.com/) ## 常見參數 ```py inplace: bool # True:更動原物件,False:直接創建新物件 axis: int # 0:列,1:欄 (table[欄][列]) index: list[str] | pd.Index # 指定是哪幾列 columns: list[str] | pd.Index # 指定是哪幾欄 ``` ## 索引 ``` A B C D E (欄/行) a 1 2 3 4 5 b 2 4 6 8 10 c 3 6 9 12 15 d 4 8 12 16 20 (列) ``` ```py df = pd.DataFrame( [[i * j for i in range(1, 6)] for j in range(1, 5)], ["a", "b", "c", "d"], ["A", "B", "C", "D", "E"], ) # ⚠️ 資料表索引 print(df["C"]["a"]) # 欄 列 print(df["C"]) # Series print(df[["C"]]) # DataFrame # ⚠️ 矩陣名稱索引 print(df.loc["a"]["C"]) # 列 行 print(df.loc["a"]) # Series print(df.loc[["a"]]) # DataFrame # ⚠️ 矩陣數字索引 print(df.iloc[0][2]) # 列 行 print(df.iloc[0]) # Series print(df.iloc[[0]]) # DataFrame ``` ## 小技巧 ### `布林 Series` ```py data = { "value": [10, 15, 8, 12, 14], "date": ["2023-10-01", "2023-10-02", "2023-10-03", "2023-10-04", "2023-10-05"], } df = pd.DataFrame(data) print(df["value"]) # 0 10 # 1 15 # 2 8 # 3 12 # 4 14 # Name: value, dtype: int64 print(df["value"] < 11) # 0 True # 1 False # 2 True # 3 False # 4 False # Name: value, dtype: bool print(df.loc[df["value"] < 11]) # value date # 0 10 2023-10-01 # 2 8 2023-10-03 ``` ## 類別 ### `pd.Index()` > Pandas 儲存【索引名稱列表】的資料結構 ### `pd.MultiIndex()` > Pandas 儲存多重【索引名稱列表】的資料結構 + 工廠函式 ```py pd.MultiIndex.from_tuples # 用 list[tuple] 創建 pd.MultiIndex.from_arrays # 用 list[list] 創建 ``` ### `pd.Series()` + 參數 ``` data : 一欄資料 name : 名稱 (串接時,作為每欄索引名稱) index : 每列索引名稱 (預設:列舉) dtype : 資料型態 copy : 深拷貝 (預設:False) ``` + 屬性 ```py ser.values # 一維 NDA ser.index # 每列索引名稱 ser.plot # 建立在 matplotlib 上的高層封裝,用它快速畫圖 # - 參數:(x="x軸索引名稱", y="y軸索引名稱", kind="圖類型") ``` + 方法 ```py # 方法 - to 系列 ser.to_frame() # 方法 - 欄位計算 (聚合函數) ser.max() # 最大值 ser.min() # 最小值 ser.mean() # 平均值 ser.median() # 中位數 ser.std() # 標準差 ser.count() # 總數量 ser.sum() # 加總 # 方法 - 映射 ser.map() # 映射 (字典) # - 範例:pd.Series(['cat', 'dog', 'bird']).map({'cat': '貓', 'dog': '狗'}) ser.apply() # 映射 (函數) # - 範例:pd.Series([1, 2, 3, 4]).apply(lambda x: x ** 2) ``` ### `pd.DataFrame()` + 參數 ``` data : 多欄資料 (字典,key 為欄索引名稱、value 為一欄資料;矩陣,每列即一列資料) index : 每列索引名稱 (預設:列舉) columns : 每欄索引名稱 (預設:列舉) dtype : 資料型態 name : 名字 copy : 深拷貝 (預設:False) ``` + 小技巧 ```py # 範例資料 data1 = {"Column1": [1, 3, 5], "Column2": [2, 4, 6]} df1 = pd.DataFrame(data1) # Column1 Column2 # 0 1 2 # 1 3 4 # 2 5 6 # 索引 df1["Column2"] # Series df1[["Column2"]] # DataFrame df1[["Column1", "Column2"]] # DataFrame # 新增一欄資料 df1["Column3"] = [-1, -2, -3, -4] # Column1 Column2 Column3 # 0 1 2 -1 # 1 3 4 -2 # 2 5 6 -3 # 3 7 8 -4 # 新增一列資料 data2 = [[7, 8]] # ⚠️ 須為矩陣 df2 = pd.DataFrame(data2, columns=df1.columns) # df2 欄索引同 df1 df1 = pd.concat([df1, df2], ignore_index=True) # ⚠️ 須為列表,忽略 df2 原有列索引,串接 # Column1 Column2 # 0 1 2 # 1 3 4 # 2 5 6 # 3 7 8 ``` + 屬性 ```py df.shape # (列,欄) df.values # 二維 NDA df.index # 每列索引名稱 df.columns # 每欄索引名稱 df.plot # 建立在 matplotlib 上的高層封裝,用它快速畫圖 # - 參數:(x="x軸索引名稱", y="y軸索引名稱", kind="圖類型") df.loc # 矩陣名稱索引 (可切片) # - df.loc["r1"]["c2"] # - df.loc["r1", "c2"] # - df.loc["r1":"r3", "c2":"c5"] # - df.loc[布林 Series] df.iloc # 矩陣數字索引 (可切片) ``` + 方法 ```py # 方法 - to 系列 df.to_pickle() # 存成 pkl 檔 df.to_hdf() # 存成 hdf 檔 df.to_csv() # 存成 csv 檔 df.to_json() # 存成 json 檔 df.to_xml() # 存成 xml 檔 df.to_excel() # 存成 xlsx 檔 df.to_markdown() # 存成 md 檔 df.to_sql() # 存入資料庫 (較支援 SQLAlchemy) ... # 方法 - 初步資料分析 df.info() # 每欄索引名稱、非空值統計、資料型態 df.describe() # 每欄最大值、最小值、中位數、平均值、標準差、四分位數 # 方法 - 過濾 df.head() # 只取前n列資料 df.tail() # 只取後n列資料 df.nlargest() # 只取前n大資料 (指定欄位) df.nsmallest() # 只取前n小資料 (指定欄位) df.query() # 查詢 # - 說明:要給一個字串化的布林判斷式 # - 範例:result = df.query('Age > 30 and Salary > 50000') # - 翻譯:尋找年齡大於 50 歲且薪水超過 5 萬的人 # 方法 - 更改 df.drop() # 丟掉某幾欄/列 df.rename() # 更改欄/列索引名稱 (給定 dict) df.reindex() # 更改欄/列索引名稱,按現有順序 (給定 list) df.set_index() # 列索引名稱 := 某欄所有值 (若給定多欄,則為 MultiIndex) df.reset_index() # 還原 set_index df.astype() # 轉換型態 # 方法 - 缺失值 df.fillna() # 填充缺失值 df.dropna() # 丟棄有缺失值的資料 # 方法 - 排序 df.sort_index() # 欄/列索引名稱排序 df.sort_values() # 值排序 (by=指定欄) df.value_counts() # 計數後排序 # 方法 - 映射 df.apply(axis=) # 映射 (函數、字典) # - 說明:不要求映射結果對齊 # - 範例:df.apply(np.sum, axis=0) df.transform(axis=) # 映射 (函數、字典) # - 說明:要求映射結果對齊 # - 範例:df.transform(lambda x: x + 1) # - 範例:df.transform({"薪水": np.log, "年齡": lambda x: x*2} ``` + 方法 ```py df.resample() # 說明 # groupby 你傳一個分類欄位 (公司),它就依照那個欄位分組 # resample 你傳一個時間頻率 (6h、1D),它就依照時間區間分組 (時間的 groupby) # 參數 # rule 切分頻率 # label 降採樣時,選擇左邊的時間還是右邊的時間 # origin 採樣開始時間,選用 "start" 即為原 DataFrame 開始時間 # closed 區間的封閉 # on 取樣欄位,若未將時間欄位設為索引,這個要設 (datetime[ns]) # 採樣 # downsample 時間降採樣,時間粒度變粗 # upsample 時間升採樣,時間粒度變細 # 方法 # asfreq() 指定填充空值 # bfill() 向後填充空值 # ffill() 向前填充空值 # 範例 df = pd.DataFrame({ "time": pd.date_range("2024-01-01", periods=10, freq="6h"), "price": [10, 12, 14, 13, 15, 20, 18, 19, 25, 30] }) df = df.set_index("time") # ✅ 建議先把時間設成索引 # 以 1 天為頻率,以平均作為新價格 downsampled = df.resample("1D")["price"].mean() print(downsampled) # 以 1 小時為頻率,以向前填充作為新價格 upsampled = df.resample("1h").ffill() print(upsampled) ``` ### `DataFrameGroupBy()` + 範例 ```py df = pd.DataFrame( { "公司": np.random.choice(["A", "B", "C"], 10), "薪水": np.random.randint(5, 50, 10), "年齡": np.random.randint(15, 50, 10), } ) # 公司 薪水 年齡 # C 13 18 # A 33 22 # A 28 22 # B 44 17 # B 23 21 # B 39 35 # A 18 44 # C 34 28 # C 13 16 # C 35 39 ``` + 產生實例 ```py df.groupby() # 樞紐分析表 # - 說明:根據某個欄位分組,分成若干個子 DataFrame # - 範例:df.groupby("公司")["薪水"].mean() # - 翻譯:【每家公司】其員工的【平均薪水】 df.groupby("公司") # DataFrameGroupBy df.groupby("公司")["薪水"] # SeriesGroupBy ``` + 方法 ```py # 方法 - 映射 df_group.apply() # 映射 (函數) # - 說明:不要求映射結果對齊、函數輸入為 Dataframe df_group.transform() # 映射 (函數) # - 說明:要求映射結果對齊、函數輸入為 Dataframe df_group.aggregate() # 聚合 (字典) # - 說明:不要求映射結果對齊 # - 範例:df.groupby("公司").agg({"薪水": ["median", "std"], "年齡": "mean"}) # - 翻譯:【每家公司】其員工的【薪水中位數、標準差】和【平均年齡】 ``` ### `SeriesGroupBy()` + 方法 ```py # 方法 - 映射 ser_group.apply() # 映射 (函數) # - 說明:不要求映射結果對齊、函數的輸入為 Series ser_group.transform() # 映射 (函數) # - 說明:要求映射結果對齊、函數的輸入為 Series ``` ## 函數 ### 讀取 > 可給 Raw Data URL ```py pd.read_pickle() # 讀取 pkl 檔 (速度比: 6) pd.read_hdf() # 讀取 hdf 檔 (速度比: 5) pd.read_csv() # 讀取 csv 檔 (速度比: 1) pd.read_json() # 讀取 json 檔 pd.read_xml() # 讀取 xml 檔 pd.read_excel() # 讀取 xlsx 檔 (速度比: 1/136) pd.read_sql() # 讀取資料庫 (較支援 SQLAlchemy) ... ``` ### 合併 + [簡單合併](https://pandas.pydata.org/docs/user_guide/merging.html#concatenating-objects) ```py pd.concat( objs=None, # list[DataFrame] | list[Series] axis=0, # {0, 1, …},要串接的軸。 join="outer", # {'inner','outer'}。如何處理其他軸上的索引。outer 用於並集,inner 用於交集。 ignore_index=False, # 是否忽略現有索引,直接以 0 ~ n-1 代替 keys=None, # 建構多層索引 (就原有的索引直接再蓋一層上去,每個串接的表可再擁有多種索引) names=None, # 每種多層索引的名稱 verify_integrity=False, # 檢查新串接的表是否包含重複項 (重度消耗資源) copy=True, # 深拷貝 ) ``` + [鍵匹配合併](https://pandas.pydata.org/docs/user_guide/merging.html#database-style-dataframe-or-named-series-joining-merging) (如 SQL 中的 JOIN) ```py pd.merge( left=None, # 左 DataFrame right=None, # 右 DataFrame how="inner", # 合併 {'left', 'right', 'outer', 'inner', 'cross'},詳見 JOIN 可視化 # 第一種選擇: 同名欄 JOIN on=None, # 黏合鍵 (須在左右 DataFrame 中找到) # 第二種選擇: 非同名欄 JOIN left_on=None, # 左黏合鍵 (須在左 DataFrame 中找到) right_on=None, # 右黏合鍵 (須在右 DataFrame 中找到) # (兩種選擇是互斥的,不能每個參數都給) left_index=False, right_index=False, sort=True, suffixes=("_x", "_y"), copy=True, indicator=False, validate=None, ) ``` ### 資料型態 ```py pd.to_datetime() pd.to_numeric() pd.to_timedelta() # object 通常是字串,或者是其他型態 # int64 np.int64 # float64 np.float64 # bool np.bool_ # datetime64 np.datetime64 # timedelta[ns] np.timedelta[ns] # category 類似枚舉,是只會出現有限集合中的字串 (例如性別:男或女) ``` ### 其他 ```py # 日期區段 pd.date_range("2024-01-01", periods=10, freq="6h") ```