1. 程式人生 > 實用技巧 >pandas - 資料結構與常用函式

pandas - 資料結構與常用函式

目錄

1. Pandas資料結構

homepage

  • DataFrame: 二維資料,類似Excel或資料庫表。

  • Series: 一維資料,既可以是一列,也可以是一行。處理方式類似於dict。

1.1. Series

1.1.1. 建立

注意,建立時需要資料值(value)及其索引 index(類似key)。

pd.Series([1, "a", 5.2, 7], index=["a", "b", "c", "d"])

## 2. 直接使用dict建立
pd.Series({"a": 1, "b": 2})

2. 訪問資料

series["a"] = "new"
series[["a", "b"]]  ## 查詢多個值

2. 屬性

  • series.index ## np.ndarray
  • series.values
  • series.name ## values的name
  • series.index.name ## index的name

2.1. DataFrame

Dataframe是一個表格型的資料結構

  • 每列可以是不同的值型別(數值、字串、布林值等)
  • 既有行索引 index,也有列索引 columns
  • 可以被看由 Series
    組成的字典

建立 Datarame 最常用的方法,包括讀取純文字檔案、excel、mysql資料庫等。

基本使用:

  1. 使用多個字典序列建立DataFrame

    data = {
        "state": ["a", "b", "c", "d"],
        "year": [2001, 2002, 2003, 2004],
        "pop": [1.6, 3.2, 7.9, 2.1]
    }
    df = pd.DataFrame(data)
    
  2. 查詢資料型別

    df.dtypes
    
  3. 查詢列索引、行索引

    df.columns
    df.index
    

2.1.1. 從DataFame中查詢Series

  • 如果只查詢一行、一列,返回的是 pd.Series
  • 如果查詢多行、多列,返回的是 pd.DataFrame

查詢列

df["a"]
df[["a", "b"]]

查詢行

df.loc[1]
df.loc[1:3]  ## 通過切片的方式獲取多行。注意:不同於list切片,loc[1:3]包含末尾元素`3`

2.2. 獲取Pandas元素

homepage

需要認識到,pandas的索引方式,是有限索引行,然後再列。你無法做到loc某一列。如果非要,可以考慮直接提取Series,然後進行操作。

  • DataFrame.at

    Access a single value for a row/column label pair.

  • DataFrame.iat

    Access a single value for a row/column pair by integer position.

  • DataFrame.loc

    Access a group of rows and columns by label(s).

  • DataFrame.iloc

    Access a group of rows and columns by integer position(s).

>>> df = pd.DataFrame([[1, 2], [4, 5], [7, 8]],
...      index=['cobra', 'viper', 'sidewinder'],
...      columns=['max_speed', 'shield'])
>>> df
            max_speed  shield
cobra               1       2
viper               4       5
sidewinder          7       8

## 3. 選取元素
>>> df.loc['cobra', 'shield']
2

## 4. 選取行返回一個series
>>> df.loc['viper']
max_speed    4
shield       5
Name: viper, dtype: int64

## 5. 選取行列返回DataFrame
>>> df.loc[['viper', 'sidewinder']]
            max_speed  shield
viper               4       5
sidewinder          7       8

With a callable, useful in method chains. The x passed to the lambda is the DataFrame being sliced. This selects the rows whose index label even.

>>> df.iloc[lambda x: x.index % 2 == 0]
      a     b     c     d
0     1     2     3     4
2  1000  2000  3000  4000

With a boolean array whose length matches the columns.

>>> df.iloc[:, [True, False, True, False]]
      a     c
0     1     3
1   100   300
2  1000  3000

5.1. 新增資料列

new_df = pd.concat([df, pd.DataFrame(columns=["h_low", "h_high", "s_low", "s_high", "v_low", "v_high"])])
new_df.h_low[idx] = 123  ## 資料賦值

5.2. 刪除行或列

DataFrame.drop(labels=None,axis=0,index=None,columns=None, inplace=False)

引數說明:

  • labels 就是要刪除的行列的名字,用列表給定
  • axis 預設為0,指刪除行,因此刪除columns時要指定axis=1
  • index 直接指定要刪除的行
  • columns 直接指定要刪除的列
  • inplace=False,預設該刪除操作不改變原資料,而是返回一個執行刪除操作後的新dataframe
  • inplace=True,則會直接在原資料上進行刪除操作,刪除後無法返回

因此,刪除行列有兩種方式:

  • labels=None,axis=0的組合
  • index或columns直接指定要刪除的行或列
## 6. df=pd.read_excel('data_1.xlsx')
df=df.drop(['學號','語文'], axis=1)
df=df.drop([1,2], axis=0)

6.1. 儲存當前的資料(序列化)

df2.to_csv("triage_new.csv")

7. 資料變換與預處理

常用函式如下

  • DataFrame.head(), Series.head()
  • DataFrame.info(), Series.info()
  • DataFrame.describe(), Series.describe()
  • Series.value_counts()
  • DataFrame.sort_index(), Series.sort_index()
  • DataFrame.sort_values(), Series.sort_values()
  • 資料清洗
    • DataFrame.isna(), Series.isna()
    • DataFrame.any(), Series.any()
    • DataFrame.dropna(), Series.dropna()
    • DataFrame.fillna(), Series.fillna()
  • Series.astype(), DataFrame.astype()
  • DataFrame.rename(), Series.rename()
  • DataFrame.set_index()
  • Series.reset_index(), DataFrame.reset_index()
  • DataFrame.drop_duplicates(), Series.drop_duplicates()
  • DataFrame.drop(), Series.drop()
  • DataFrame.isin(), Series.isin()
  • pd.cut()
  • pd.qcut()
  • DataFrame.where(), Series.where()
  • pd.concat()
  • DataFrame.pivot_table()
用於演示的資料如下:

In [15]: data
Out[15]:
  company  salary  age
0     NaN      43   21
1       A       8   41
2       A      28   26
3       C      42   28
4       A      33   26
5       C      20   18
6       A      48   43
7       B      25   23
8       B      39   18
  • DataFrame.head(), Series.head()

    返回DataFrame的前N行。當資料量較大時,使用 .head() 可以快速對資料有個大致瞭解。

  • DataFrame.info(), Series.info()

    列印所用資料的一些基本資訊,包括索引和列的資料型別和佔用的記憶體大小。

    data.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 9 entries, 0 to 8
    Data columns (total 3 columns):
    company    8 non-null object
    salary     9 non-null int32
    age        9 non-null int32
    dtypes: int32(2), object(1)
    memory usage: 224.0+ bytes
    
  • DataFrame.describe(), Series.describe()

    主要用途:生成描述性統計彙總,包括資料的計數和百分位數,有助於瞭解大致的資料分佈。

    ## 預設生成數值列的描述性統計
    ## 使用 include = 'all'生成所有列
    In [18]: data.describe()
    Out[18]:
              salary        age
    count   9.000000   9.000000
    mean   31.777778  27.111111
    std    12.804079   9.143911
    min     8.000000  18.000000
    25%    25.000000  21.000000
    50%    33.000000  26.000000
    75%    42.000000  28.000000
    max    48.000000  43.000000
    
  • Series.value_counts()

    統計分類變數中每個類的數量,比如company中各個公司都有多少人

    主要引數:

    • normalize: boolean, default False, 返回各類的佔比
    • sort: boolean, default True, 是否對統計結果進行排序
    • ascending: boolean, default False, 是否升序排列
    In [19]: data['company'].value_counts()
    Out[19]:
    A    4
    B    2
    C    2
    Name: company, dtype: int64
    

    返回佔比情況

    In [20]: data['company'].value_counts(normalize=True)
    Out[20]:
    A    0.50
    B    0.25
    C    0.25
    Name: company, dtype: float64
    

    升序排列

    In [21]: data['company'].value_counts(ascending=True)
    Out[21]:
    C    2
    B    2
    A    4
    Name: company, dtype: int64
    
  • DataFrame.sort_index(), Series.sort_index()

    對資料按照索引進行排序

    主要引數:

    • ascending: boolean, default False, 是否升序排列
    • inplace: boolean, default False, 是否作用於原物件
    ## 按索引降序排列
    In [27]: data.sort_index(ascending=False)
    Out[27]:
      company  salary  age
    8       B      39   18
    7       B      25   23
    6       A      48   43
    5       C      20   18
    4       A      33   26
    3       C      42   28
    2       A      28   26
    1       A       8   41
    0     NaN      43   21
    
  • DataFrame.sort_values(), Series.sort_values()

    對DataFrame而言,按照某列進行排序(用by引數控制),對Series按資料列進行排序。

    主要引數:

    • by: str or list of str, 作用於DataFrame時需要指定排序的列
    • ascending: boolean, default False, 是否升序排列
    In [28]: data.sort_values(by='salary')
    Out[28]:
      company  salary  age
    1       A       8   41
    5       C      20   18
    7       B      25   23
    2       A      28   26
    4       A      33   26
    8       B      39   18
    3       C      42   28
    0     NaN      43   21
    6       A      48   43
    
  • 資料清洗

    • DataFrame.isna(), Series.isna()

    判斷資料是否為缺失值,是的話返回True,否的話返回False

    In [22]: data.isna()
    Out[22]:
       company  salary    age
    0     True   False  False
    1    False   False  False
    2    False   False  False
    3    False   False  False
    4    False   False  False
    5    False   False  False
    6    False   False  False
    7    False   False  False
    8    False   False  False
    
    • DataFrame.any(), Series.any()

    大多數情況下資料量較大,不可能直接isna()後一個一個看是否是缺失值。any()和isna()結合使用可以判斷某一列是否有缺失值。

    In [23]: data.isna().any()
    Out[23]:
    company     True
    salary     False
    age        False
    dtype: bool
    
    • DataFrame.dropna(), Series.dropna()

    刪掉含有缺失值的資料。

    In [24]: data.dropna()
    Out[24]:
      company  salary  age
    1       A       8   41
    2       A      28   26
    3       C      42   28
    4       A      33   26
    5       C      20   18
    6       A      48   43
    7       B      25   23
    8       B      39   18
    
    • DataFrame.fillna(), Series.fillna()

    填充缺失資料

    主要引數:

    • value: scalar, dict, Series, or DataFrame, 用於填充缺失值的值
    • method: {‘backfill’, ‘bfill’, ‘pad’, ‘ffill’, None}, default None, 缺失值的充方式,常用的是bfill後面的值進行填充,ffill用前面的值進行填充
    • inplace: boolean, default False, 是否作用於原物件
    In [26]: data.fillna('B')
    Out[26]:
      company  salary  age
    0       B      43   21
    1       A       8   41
    2       A      28   26
    3       C      42   28
    4       A      33   26
    5       C      20   18
    6       A      48   43
    7       B      25   23
    8       B      39   18
    

    用缺失值後面的值來填充(這裡NaN後面是'A')

    In [25]: data.fillna(method='bfill')
    Out[25]:
      company  salary  age
    0       A      43   21
    1       A       8   41
    2       A      28   26
    3       C      42   28
    4       A      33   26
    5       C      20   18
    6       A      48   43
    7       B      25   23
    8       B      39   18
    
  • Series.astype(), DataFrame.astype()

    修改欄位的資料型別,資料量大的情況下可用於減小資料佔用的記憶體,多用於Series。

    ## 把age欄位轉為int型別
    In [12]: data["age"] = data["age"].astype(int)
    
    In [13]: data
    Out[13]:
      company  gender  salary  age
    0       B  female      30   40
    1       A  female      36   31
    2       B  female      35   28
    3       B  female       9   18
    4       B  female      16   43
    5       A    male      46   22
    6       B  female      15   28
    7       B  female      33   40
    8       C    male      19   32
    
  • DataFrame.rename(), Series.rename()

    多用於修改DataFrame的列名

    主要引數:

    • columns: dict-like or function, 指定要修改的列名以及新的列名,一般以字典形式傳入
    • inplace: boolean, default False, 是否作用於原物件
    ## 將'age'更改為員工編號'number',並作用於原物件
    In [15]: data.rename(columns={'age':'number'},inplace=True)
    
    In [16]: data
    Out[16]:
      company  gender  salary  number
    0       B  female      30      40
    1       A  female      36      31
    2       B  female      35      28
    3       B  female       9      18
    4       B  female      16      43
    5       A    male      46      22
    6       B  female      15      28
    7       B  female      33      40
    8       C    male      19      32
    
  • DataFrame.set_index()

    將DataFrame中的某一(多)個欄位設定為索引

    In [19]: data.set_index('number',inplace=True)
    
    In [20]: data
    Out[20]:
           company  gender  salary
    number
    40           B  female      30
    31           A  female      36
    28           B  female      35
    18           B  female       9
    43           B  female      16
    22           A    male      46
    28           B  female      15
    40           B  female      33
    32           C    male      19
    
  • Series.reset_index(), DataFrame.reset_index()

    主要用途:重置索引,預設重置後的索引為0~len(df)-1

    主要引數:

    • drop: boolean, default False, 是否丟棄原索引,具體看下方演示
    • inplace: boolean, default False, 是否作用於原物件
    ## drop = True,重置索引,並把原有的索引丟棄
    In [22]: data.reset_index(drop=True)
    Out[22]:
      company  gender  salary
    0       B  female      30
    1       A  female      36
    2       B  female      35
    3       B  female       9
    4       B  female      16
    5       A    male      46
    6       B  female      15
    7       B  female      33
    8       C    male      19
    
    ## drop = False,重置索引
    ## 原索引列'number'作為新欄位進入DataFrame
    In [23]: data.reset_index(drop=False,inplace=True)
    
    In [24]: data
    Out[24]:
       number company  gender  salary
    0      40       B  female      30
    1      31       A  female      36
    2      28       B  female      35
    3      18       B  female       9
    4      43       B  female      16
    5      22       A    male      46
    6      28       B  female      15
    7      40       B  female      33
    8      32       C    male      19
    
  • DataFrame.drop_duplicates(), Series.drop_duplicates()

    主要用途:去掉重複值,作用和SQL中的distinct類似

    In [26]: data['company'].drop_duplicates()
    Out[26]:
    0    B
    1    A
    8    C
    Name: company, dtype: object
    
  • DataFrame.drop(), Series.drop()

    主要用途:常用於刪掉DataFrame中的某些欄位

    主要引數:

    • columns: single label or list-like, 指定要刪掉的欄位
    ## 刪掉'gender'列
    In [27]: data.drop(columns = ['gender'])
    Out[27]:
       number company  salary
    0      40       B      30
    1      31       A      36
    2      28       B      35
    3      18       B       9
    4      43       B      16
    5      22       A      46
    6      28       B      15
    7      40       B      33
    8      32       C      19
    
  • DataFrame.isin(), Series.isin()

    主要用途:常用於構建布林索引,對DataFrame的資料進行條件篩選

    ## 篩選出A公司和C公司的員工記錄
    In [29]: data.loc[data['company'].isin(['A','C'])]
    Out[29]:
       number company  gender  salary
    1      31       A  female      36
    5      22       A    male      46
    8      32       C    male      19
    
  • pd.cut()

    主要用途:將連續變數離散化,比如將人的年齡劃分為各個區間

    主要引數:

    • x: array-like, 需要進行離散化的一維資料
    • bins: int, sequence of scalars, or IntervalIndex, 設定需要分成的區間,可以指定區間數量,也可以指定間斷點
    • labels: array or bool, optional, 設定區間的標籤
    ## 把薪水分成5個區間
    In [33]: pd.cut(data.salary,bins = 5)
    Out[33]:
    0     (23.8, 31.2]
    1     (31.2, 38.6]
    2     (31.2, 38.6]
    3    (8.963, 16.4]
    4    (8.963, 16.4]
    5     (38.6, 46.0]
    6    (8.963, 16.4]
    7     (31.2, 38.6]
    8     (16.4, 23.8]
    Name: salary, dtype: category
    Categories (5, interval[float64]): [(8.963, 16.4] < (16.4, 23.8] < (23.8, 31.2] < (31.2, 38.6] <(38.6, 46.0]]
    
    ## 自行指定間斷點
    In [32]: pd.cut(data.salary,bins = [0,10,20,30,40,50])
    Out[32]:
    0    (20, 30]
    1    (30, 40]
    2    (30, 40]
    3     (0, 10]
    4    (10, 20]
    5    (40, 50]
    6    (10, 20]
    7    (30, 40]
    8    (10, 20]
    Name: salary, dtype: category
    Categories (5, interval[int64]): [(0, 10] < (10, 20] < (20, 30] < (30, 40] < (40, 50]]
    
    ## 指定區間的標籤
    In [34]: pd.cut(data.salary,bins = [0,10,20,30,40,50],labels = ['低','中下','中','中上','高'])
    Out[34]:
    0     中
    1    中上
    2    中上
    3     低
    4    中下
    5     高
    6    中下
    7    中上
    8    中下
    Name: salary, dtype: category
    Categories (5, object): [低 < 中下 < 中 < 中上 < 高]
    
  • pd.qcut()

    主要用途:將連續變數離散化,區別於pd.cut()用具體數值劃分,pd.qcut()使用百分比進行區間劃分

    主要引數:

    • x: array-like, 需要進行離散化的一維資料
    • q: integer or array of quantiles, 置需要分成的區間,可以指定區間格式,也可以指定間斷點
    • labels: array or boolean, default None, 設定區間的標籤
    ## 按照0-33.33%,33.33%-66.67%,66.67%-100%百分位進行劃分
    In [35]: pd.qcut(data.salary,q = 3)
    Out[35]:
    0    (18.0, 33.667]
    1    (33.667, 46.0]
    2    (33.667, 46.0]
    3     (8.999, 18.0]
    4     (8.999, 18.0]
    5    (33.667, 46.0]
    6     (8.999, 18.0]
    7    (18.0, 33.667]
    8    (18.0, 33.667]
    Name: salary, dtype: category
    Categories (3, interval[float64]): [(8.999, 18.0] < (18.0, 33.667] < (33.667, 46.0]]
    
  • DataFrame.where(), Series.where()

    主要用途:將不符合條件的值替換掉成指定值,相當於執行了一個if-else

    主要引數:

    • cond: boolean Series/DataFrame, array-like, or callable, 用於篩選的條件
    • other: scalar, Series/DataFrame, or callable, 對不符合cond條件的值(結果為為False),用other的值進行替代
    ## 語句解析
    ## 若salary<=40,則保持原來的值不變
    ## 若salary大於40,則設定為40
    In [38]: data['salary'].where(data.salary<=40,40)
    Out[38]:
    0    30
    1    36
    2    35
    3     9
    4    16
    5    40
    6    15
    7    33
    8    19
    Name: salary, dtype: int32
    
  • pd.concat()

    主要用途:將多個Series或DataFrame拼起來(橫拼或者豎拼都可以)

    主要引數:

    • objs: a sequence or mapping of Series or DataFrame objects, 用於拼接的Series或DataFrame,一般都放在一個列表中傳入
    • axis: 0/’index’, 1/’columns’, 控制資料是橫向拼接還是縱向拼接,預設為縱向拼接。
    • ignore_index: bool, default False, 是否保留原Seires或DataFrame內部的索引,如果為True則對拼接而成的資料生成新索引(0~n-1)
    ## 分別取data的前三條和後三條為data1和data2
    In [41]: data1 = data.head(3)
    In [42]: data1
    Out[42]:
       number company  gender  salary
    0      40       B  female      30
    1      31       A  female      36
    2      28       B  female      35
    
    In [43]: data2 = data.tail(3)
    In [44]: data2
    Out[44]:
       number company  gender  salary
    6      28       B  female      15
    7      40       B  female      33
    8      32       C    male      19
    
    ## 拼接資料
    In [45]: pd.concat([data1,data2],ignore_index = False)
    Out[45]:
       number company  gender  salary
    0      40       B  female      30
    1      31       A  female      36
    2      28       B  female      35
    6      28       B  female      15
    7      40       B  female      33
    8      32       C    male      19
    
    ## 拼接資料並重置索引
    In [46]: pd.concat([data1,data2],ignore_index=True)
    Out[46]:
       number company  gender  salary
    0      40       B  female      30
    1      31       A  female      36
    2      28       B  female      35
    3      28       B  female      15
    4      40       B  female      33
    5      32       C    male      19
    
  • DataFrame.pivot_table()

    主要用途:對DataFrame進行資料透視,相當於Excel中的資料透視表

    主要引數:

    • values: column to aggregate, optional, 用於聚合運算的欄位(資料透視的目標變數)
    • index: column, Grouper, array, or list of the previous, 類比於資料透視表中的行標籤
    • columns: column, Grouper, array, or list of the previous, 類比於資料透視表中的列標籤
    • aggfunc: function, list of functions, dict, default numpy.mean, 對values進行什麼聚合運算
    ## 從公司和性別兩個維度對薪水進行資料透視
    ## 看看這兩個維度下的平均薪資水平
    In [47]: data.pivot_table(values = 'salary',index = 'company',
                              columns = 'gender',aggfunc=np.mean)
    Out[47]:
    gender   female  male
    company
    A          36.0  46.0
    B          23.0   NaN
    C           NaN  19.0
    

8. 資料統計函式

Github: (資料科學學習手札69)詳解pandas中的map、apply、applymap、groupby、agg

本文就將針對pandas中的map()、apply()、applymap()、groupby()、agg()等方法展開詳細介紹,並結合實際例子幫助大家更好地理解它們的使用技巧。

8.1. 資料載入

首先讀入資料,這裡使用到的全美嬰兒姓名資料,包含了1880-2018年全美每年對應每個姓名的新生兒資料,在jupyterlab中讀入資料並列印資料集的一些基本資訊以瞭解我們的資料集:

import pandas as pd

## 9. 讀入資料
data = pd.read_csv('data.csv')
data.head()

檢視各列資料型別、資料框行列數

print(data.dtypes)
print()
print(data.shape)

9.1. 非聚合類方法(無需分組)

這裡的非聚合指的是資料處理前後沒有進行分組操作,資料列的長度沒有發生改變,因此本章節中不涉及groupby()。

  • map()

    字典對映

    ## 定義F->女性,M->男性的對映字典
    gender2xb = {'F': '女性', 'M': '男性'}
    ## 利用map()方法得到對應gender列的對映列
    data.gender.map(gender2xb)
    

    對映函式

    ## 因為已經知道資料gender列性別中只有F和M所以編寫如下lambda函式
    data.gender.map(lambda x:'女性' if x is 'F' else '男性')
    

    特殊物件

    ## 一些接收單個輸入值且有輸出的物件也可以用map()方法來處理
    data.gender.map("This kid's gender is {}".format)
    

    map()還有一個引數na_action,類似R中的na.action,取值為None或ingore,用於控制遇到缺失值的處理方式,設定為ingore時序列運算過程中將忽略Nan值原樣返回。

  • apply()

    apply()堪稱pandas中最好用的方法,其使用方式跟map()很像,主要傳入的主要引數都是接受輸入返回輸出。

    但相較於map()針對單列Series進行處理,一條apply()語句可以對單列或多列進行運算,覆蓋非常多的使用場景。

    data.gender.apply(lambda x:'女性' if x is 'F' else '男性')
    ## 可以看到這裡實現了跟map()一樣的功能
    

    apply()最特別的地方在於其可以同時處理多列資料,我們先來了解一下如何處理多列資料輸入單列資料輸出的情況。

    譬如這裡我們編寫一個使用到多列資料的函式用於拼成對於每一行描述性的話,並在apply()用lambda函式傳遞多個值進編寫好的函式中(當呼叫DataFrame.apply()時,apply()在序列過程中實際處理的是每一行資料,而不是Series.apply()那樣每次處理單個值)。

    def generate_descriptive_statement(year, name, gender, count):
        year, count = str(year), str(count)
        gender = '女性' if gender is 'F' else '男性'
        return f'在{year}年,叫做{name}性別為{gender}的新生兒有{count}個。'
    
    ## 注意在處理多個值時要給apply()新增引數axis=1
    data.apply(lambda row:generate_descriptive_statement(row['year'],
                                                         row['name'],
                                                         row['gender'],
                                                         row['count']),
                                                         axis = 1)
    

    輸出多列資料:有些時候我們利用apply()會遇到希望同時輸出多列資料的情況,在apply()中同時輸出多列時實際上返回的是一個Series,這個Series中每個元素是與apply()中傳入函式的返回值順序對應的元組。

    比如下面我們利用apply()來提取name列中的首字母和剩餘部分字母:

    data.apply(lambda row: (row['name'][0], row['name'][1:]), axis=1)
    

    可以看到,這裡返回的是單列結果,每個元素是返回值組成的元組,這時若想直接得到各列分開的結果,需要用到zip(*zipped)來解開元組序列,從而得到分離的多列返回值:

    a, b = zip(*data.apply(lambda row: (row['name'][0], row['name'][1:]), axis=1))
    print(a[:10])
    print(b[:10])
    
  • applymap()

    applymap()是與map()方法相對應的專屬於DataFrame物件的方法,類似map()方法傳入函式、字典等,傳入對應的輸出結果。

    不同的是applymap()將傳入的函式等作用於整個資料框中每一個位置的元素,因此其返回結果的形狀與原資料框一致。

    譬如下面的簡單示例,我們把嬰兒姓名資料中所有的字元型資料訊息小寫化處理,對其他型別則原樣返回:

    def lower_all_string(x):
        if isinstance(x, str):
            return x.lower()
        else:
            return x
    
    data.applymap(lower_all_string)
    

9.2. 聚合類方法

有些時候我們需要像SQL裡的聚合操作那樣將原始資料按照某個或某些離散型的列進行分組再求和、平均數等聚合之後的值,在pandas中分組運算是一件非常優雅的事。

9.2.1. 利用groupby()進行分組

要進行分組運算第一步當然就是分組,在pandas中對資料框進行分組使用到groupby()方法。

其主要使用到的引數為by,這個引數用於傳入分組依據的變數名稱,當變數為1個時傳入名稱字串即可。

當為多個時傳入這些變數名稱列表,DataFrame物件通過groupby()之後返回一個生成器,需要將其列表化才能得到需要的分組後的子集,如下面的示例:

## 10. 按照年份和性別對嬰兒姓名資料進行分組
groups = data.groupby(by=['year','gender'])
## 11. 檢視groups型別
type(groups)

可以看到它此時是生成器,下面我們用列表解析的方式提取出所有分組後的結果:

## 12. 利用列表解析提取分組結果
groups = [group for group in groups]

檢視其中的一個元素,可以看到每一個結果都是一個二元組,元組的第一個元素是對應這個分組結果的分組組合方式,第二個元素是分組出的子集資料框,而對於DataFrame.groupby()得到的結果。

直接呼叫聚合函式

譬如這裡我們提取count列後直接呼叫max()方法:

## 13. 求每個分組中最高頻次
data.groupby(by=['year','gender'])['count'].max()

注意這裡的year、gender列是以索引的形式存在的,想要把它們還原回資料框,使用reset_index(drop=False)即可:

data.groupby(by=['year','gender'])['count'].max().reset_index(drop=False)
結合apply()

分組後的結果也可以直接呼叫apply(),這樣可以編寫更加自由的函式來完成需求,譬如下面我們通過自編函式來求得每年每種性別出現頻次最高的名字及對應頻次。

要注意的是,這裡的apply傳入的物件是每個分組之後的子資料框,所以下面的自編函式中直接接收的df引數即為每個分組的子資料框:

import numpy as np

def find_most_name(df):
    return str(np.max(df['count']))+'-'+df['name'][np.argmax(df['count'])]

data.groupby(['year','gender']).apply(find_most_name).reset_index(drop=False)

13. 利用agg()進行更靈活的聚合

agg即aggregate,聚合,在pandas中可以利用agg()對Series、DataFrame以及groupby()後的結果進行聚合。

其傳入的引數為字典,鍵為變數名,值為對應的聚合函式字串,譬如 {'v1':['sum','mean'], 'v2':['median','max','min]} 就代表對資料框中的v1列進行求和、均值操作,對v2列進行中位數、最大值、最小值操作。

下面用幾個簡單的例子演示其具體使用方式:

聚合Series

在對Series進行聚合時,因為只有1列,所以可以不使用字典的形式傳遞引數,直接傳入函式名列表即可:

## 14. 求count列的最小值、最大值以及中位數
data['count'].agg(['min','max','median'])

聚合資料框

對資料框進行聚合時因為有多列,所以要使用字典的方式傳入聚合方案:

 data.agg({'year': ['max','min'], 'count': ['mean','std']})

值得注意的是,因為上例中對於不同變數的聚合方案不統一,所以會出現NaN的情況。

聚合groupby()結果

data.groupby(['year','gender']).agg({'count':['min','max','median']}).reset_index(drop=False)

可以注意到雖然我們使用reset_index()將索引列還原回變數,但聚合結果的列名變成紅色框中奇怪的樣子,而在pandas 0.25.0以及之後的版本中,可以使用pd.NamedAgg()來為聚合後的每一列賦予新的名字:

data.groupby(['year','gender']).agg(
    min_count=pd.NamedAgg(column='count', aggfunc='min'),
    max_count=pd.NamedAgg(column='count', aggfunc='max'),
    median=pd.NamedAgg(column='count', aggfunc='median')).reset_index(drop=False)

14.1. groupby 詳解

df.groupby("A").sum()
df.groupby(["A", "B"]).mean()
df.groupby(["A", "B"], as_index=False).mean()
df.groupby("A").agg([np.sum, np.mean, np.std])
df.groupby("A")["C"].agg([np.sum, np.mean, np.std])
df.groupby("A").agg([np.sum, np.mean, np.std])["C"]  ## 同上
df.groupby("A").agg({"C": np.sum, "D": np.maen})

14.1.1. GroupBy遵從split、apply、combine模式

14.1.2. 歸一化:電影評分

def normalize(df):
    min_value = df["Rating"].min()
    max_value = df["Rating"].max()
    df["Rating_norm"] = df["Roting"].apply(
        lambda x: (x-min_value)/(max_value-min_value))
    return df
def top_2(df, n=2):
    return df.sort_values(by="Rating")[[]]

df.groupby("manth").apply(get_)