Tìm hiểu Pandas (Bài 3): Group, Merge dữ liệu

Ở bài này, ta sẽ giải quyết câu hỏi làm thế nào để sắp xếp lại cấu trúc dữ liệu phục vụ cho mục đích phù hợp. Ta sẽ sử dụng một số hàm phổ biến như: groupby, concat, aggregate, append,.. qua các ví dụ với tập dữ liệu thực để hiểu rõ hơn. (Các ví dụ được thao tác trên python 3.7.0 và pandas 0.23.4)

Các mục thảo luận gồm:

  • Nhóm dữ liệu (grouping of data)
  • Nối dữ liệu (merging and concatenating data)

I. Nhóm dữ liệu (grouping of data)

Groupby là phép toán thực hiện trên DataFrames, khi thực thi sẽ thực hiện 3 việc:

  • Chia tập dữ liệu
  • Phân tích dữ liệu
  • Nhóm dữ liệu

Hàm groupby:

DataFrame.groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False, observed=False, **kwargs)

Tham số thường dùng:

  • by : mapping, function, label, hoặc danh sách các label. Xác định nhóm để groupby.
  • axis : int, 0: group theo hàng, 1: group theo cột (mặc định 0)
  • level : int/string, chỉ số level hoặc tên cột, mặc định None. Sử dụng với MultiIndex, để chỉ rõ lấy index nào
  • as_index : boolean, mặc định True. Giá trị True: kết quả trả về column được group sẽ là key.
  • sort : boolean, mặc định True. Sắp xếp key, để tăng hiệu suất thì hãy để False.

Group một cột

Giờ cùng thử thao tác với tập dữ liệu các đội bóng vô địch UEFA Champions League: euro_winners.csv

In [1]: import pandas as pd
        uefaDF=pd.read_csv('./euro_winners.csv')
In [2]: uefaDF.head()

In [4]: nationsGrp=uefaDF.groupby('Nation');
         type(nationsGrp)
Out[5]: pandas.core.groupby.DataFrameGroupBy

Kiểu của nationsGrppandas.core.groupby.DataFrameGroupBy. Cột được sử dụng để groupby sẽ là key. Sử dụng thuộc tính “groups”:

In [6]: nationsGrp.groups
Out[7]:{'England': IntIndex64([12, 21, 22, 23, 24, 25, 26, 28, 43, 49, 52, 56], dtype='int64'),
'France':IntIndex64([37], dtype='int64'),
'Germany':IntIndex64([18, 19, 20, 27, 41, 45, 57], dtype='int64'),
'Italy':IntIndex64([7, 8, 9, 13, 29, 33, 34, 38, 40, 47, 51, 54], dtype='int64'),
'Netherlands':IntIndex64([14, 15, 16, 17, 32, 39], dtype='int64'),
'Portugal':IntIndex64([5, 6, 31, 48], dtype='int64'),
'Romania':IntIndex64([30], dtype='int64'),
'Scotland':IntIndex64([11], dtype='int64'),
'Spain':IntIndex64([0, 1, 2, 3, 4, 10, 36, 42, 44, 46, 50, 53, 55], dtype='int64'),
'Yugoslavia':IntIndex64([35], dtype='int64')}

Về cơ bản, đây là dạng dictionary cho thấy tên nhóm riêng biệt và index tương ứng.

Để lấy được số quốc gia có FC vô địch (số lượng group), số FC vô địch theo từng nước (số lượng bản ghi trong từng group):

In [8]: len(nationsGrp.groups)
Out[9]: 10
In [10]: nationWins=nationsGrp['Winners'].count();
        nationWins
Out[11]:
Nation
England        12
France          1
Germany         7
Italy          12
Netherlands     6
Portugal        4
Romania         1
Scotland        1
Spain          13
Yugoslavia      1
dtype: int64

Để đưa về dạng DataFrame, sử dụng reset_index()

In [12]: nationWins.reset_index(name='FC_count')
Out[13]: 
        Nation  FC_count
0      England        12
1       France         1
2      Germany         7
3        Italy        12
4  Netherlands         6
5     Portugal         4
6      Romania         1
7     Scotland         1
8        Spain        13
9   Yugoslavia         1

Thuộc tính ‘name‘ để thay đổi tên cột kết quả, ở đây là số lượng FC vô địch.

Thay vì sử dụng hàm reset_index(), ta có thể sử dụng thuộc tính as_index =False khi groupby cũng sẽ cho kết quả tương đương.

In [14]: nationsGrp=uefaDF.groupby('Nation', as_index=False)['Winners'].count();

Group nhiều cột

Để xem số lần vô địch của từng FC theo từng quốc gia:

In [15]: winnersGrp =uefaDF.groupby(['Nation','Winners'])
          clubWins=winnersGrp.size()
          clubWins
Out[16]: 
Nation       Winners          
England      Aston Villa          1
             Chelsea              1
             Liverpool            5
             Manchester United    3
             Nottingham Forest    2
France       Marseille            1
Germany      Bayern Munich        5
             Borussia Dortmund    1
             Hamburg              1
Italy        Internazionale       3
             Juventus             2
             Milan                7
Netherlands  Ajax                 4
             Feyenoord            1
             PSV Eindhoven        1
Portugal     Benfica              2
             Porto                2
Romania      Steaua Bucure?ti     1
Scotland     Celtic               1
Spain        Barcelona            4
             Real Madrid          9
Yugoslavia   Red Star Belgrade    1
dtype: int64

Một chút khác biệt so với khi group một cột đó là group nhiều cột thì các cột này phải được đưa dưới dạng list (trong dấu [])

Như vậy bước đầu ta có thể hiểu được cách hoạt động của groupby và cách sử dụng.

Group với MultiIndex

Ta có thể group by theo level với DataFrame có MultiIndex. Dữ liệu: goal_stats_euro_leagues_2012-13.csv

In [17]: goalStatsDF=pd.read_csv('./goal_stats_euro_leagues_2012-13.csv')
         goalStatsDF=goalStatsDF.set_index(['Month','Stat'])
         goalStatsDF		
Out[18]: 
                            EPL  La Liga  Serie A  Bundesliga
Month      Stat                                              
08/01/2012 MatchesPlayed   20.0       20     10.0        10.0
09/01/2012 MatchesPlayed   38.0       39     50.0        44.0
10/01/2012 MatchesPlayed   31.0       31     39.0        27.0
11/01/2012 MatchesPlayed   50.0       41     42.0        46.0
12/01/2012 MatchesPlayed   59.0       39     39.0        26.0
01/01/2013 MatchesPlayed   42.0       40     40.0        18.0
02/01/2013 MatchesPlayed   30.0       40     40.0        36.0
03/01/2013 MatchesPlayed   35.0       38     39.0        36.0
04/01/2013 MatchesPlayed   42.0       42     41.0        36.0
05/01/2013 MatchesPlayed   33.0       40     40.0        27.0
06/02/2013 MatchesPlayed    NaN       10      NaN         NaN
08/01/2012 GoalsScored     57.0       60     21.0        23.0
09/01/2012 GoalsScored    111.0      112    133.0       135.0
10/01/2012 GoalsScored     95.0       88     97.0        77.0
11/01/2012 GoalsScored    121.0      116    120.0       137.0
12/01/2012 GoalsScored    183.0      109    125.0        72.0
01/01/2013 GoalsScored    117.0      121    104.0        51.0
02/01/2013 GoalsScored     87.0      110    100.0       101.0
03/01/2013 GoalsScored     91.0      101     99.0       106.0
04/01/2013 GoalsScored    105.0      127    102.0       104.0
05/01/2013 GoalsScored     96.0      109    102.0        92.0
06/01/2013 GoalsScored      NaN       80      NaN         NaN

Theo thứ tự set index: Month = 0, Stat = 1 nên khi group theo level = 1 hay level = ‘Stat’ sẽ đều cho kết quả tương đương.

In [19]: monthStatGroup=goalStatsDF.groupby(level=1).count()
     	 monthStatGroup
Out[20]: 
               EPL  La Liga  Serie A  Bundesliga
Stat                                            
GoalsScored     10       11       10          10
MatchesPlayed   10       11       10          10

Thao tác với hàm aggregate:

In [21]: import numpy as np
       monthStatGroup=goalStatsDF.groupby(level='Stat')
       monthStatGroup.agg(np.sum)
Out[22]: 
                  EPL  La Liga  Serie A  Bundesliga
Stat                                               
GoalsScored    1063.0     1133   1003.0       898.0
MatchesPlayed   380.0      380    380.0       306.0

Chú ý rằng giá trị NaN sẽ không được tính trong phép toán agg.

Có thể truyền nhiều hàm cùng lúc:

In [23]: monthStatGroup.agg([np.sum, np.mean,np.size])
Out[24]: 
                  EPL              La Liga                  Serie A         \
                  sum   mean  size     sum        mean size     sum   mean   
Stat                                                                         
GoalsScored    1063.0  106.3  11.0    1133  103.000000   11  1003.0  100.3   
MatchesPlayed   380.0   38.0  11.0     380   34.545455   11   380.0   38.0   

                    Bundesliga              
               size        sum  mean  size  
Stat                                        
GoalsScored    11.0      898.0  89.8  11.0  
MatchesPlayed  11.0      306.0  30.6  11.0

Cũng thể chỉ định cột nào sử dụng hàm nào

In [25]: monthStatGroup.agg({'EPL':np.sum, 'La Liga':np.mean})
Out[26]:       
                  La Liga     EPL
Stat                             
GoalsScored    103.000000  1063.0
MatchesPlayed   34.545455   380.0

II. Nối dữ liệu (merging and concatenating of data)

Hàm concat

Hàm concat dùng để nối các dữ liệu cấu trúc pandas với nhau.

pandas.concat(objsaxis=0join=’outer’join_axes=Noneignore_index=Falsekeys=Nonelevels=Nonenames=Noneverify_integrity=Falsecopy=True)

Tham số thường dùng:

  • objs: danh sách các object Series, DataFrame, hoặc Panel 
  • axis: int, 0: concat theo cột, 1: concat theo hàng (mặc định 0)
  • join: inner/outer (mặc định outer)
  • ignore_index: boolean (mặc định False), giá trị True: giá trị index sẽ không được sử dụng trong khi concat. Kết quả trả về index được đánh lại từ 0
In [1]: df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
   ...:                     'B': ['B0', 'B1', 'B2', 'B3'],
   ...:                     'C': ['C0', 'C1', 'C2', 'C3'],
   ...:                     'D': ['D0', 'D1', 'D2', 'D3']},
   ...:                     index=[0, 1, 2, 3])

In [2]: df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
   ...:                     'B': ['B4', 'B5', 'B6', 'B7'],
   ...:                     'C': ['C4', 'C5', 'C6', 'C7'],
   ...:                     'D': ['D4', 'D5', 'D6', 'D7']},
   ...:                      index=[4, 5, 6, 7])
   ...: 

In [3]: df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
   ...:                     'B': ['B8', 'B9', 'B10', 'B11'],
   ...:                     'C': ['C8', 'C9', 'C10', 'C11'],
   ...:                     'D': ['D8', 'D9', 'D10', 'D11']},
   ...:                     index=[8, 9, 10, 11])
   ...:

In [4]: pd.concat([df1, df3, df2])
      A    B    C    D
0    A0   B0   C0   D0
1    A1   B1   C1   D1
2    A2   B2   C2   D2
3    A3   B3   C3   D3
8    A8   B8   C8   D8
9    A9   B9   C9   D9
10  A10  B10  C10  D10
11  A11  B11  C11  D11
4    A4   B4   C4   D4
5    A5   B5   C5   D5
6    A6   B6   C6   D6
7    A7   B7   C7   D7

Sử dụng ignore_index:

In [5]: pd.concat([df1, df3, df2], ignore_index=True)
      A    B    C    D
0    A0   B0   C0   D0
1    A1   B1   C1   D1
2    A2   B2   C2   D2
3    A3   B3   C3   D3
4    A8   B8   C8   D8
5    A9   B9   C9   D9
6   A10  B10  C10  D10
7   A11  B11  C11  D11
8    A4   B4   C4   D4
9    A5   B5   C5   D5
10   A6   B6   C6   D6
11   A7   B7   C7   D7

Sử dụng logic để nối, nếu không chỉ định tham số join thì mặc định là sẽ ‘outer’

In [6]: df4 = pd.DataFrame({'B': ['B2', 'B3', 'B6', 'B7'],
   ...:                     'D': ['D2', 'D3', 'D6', 'D7'],
   ...:                     'F': ['F2', 'F3', 'F6', 'F7']},
   ...:                    index=[2, 3, 6, 7])
   ...: 

In [7]: pd.concat([df1, df4], axis=1)
     A    B    C    D    B    D    F
0   A0   B0   C0   D0  NaN  NaN  NaN
1   A1   B1   C1   D1  NaN  NaN  NaN
2   A2   B2   C2   D2   B2   D2   F2
3   A3   B3   C3   D3   B3   D3   F3
6  NaN  NaN  NaN  NaN   B6   D6   F6
7  NaN  NaN  NaN  NaN   B7   D7   F7

Chỉ định tham số join=’inner’:

In [8]: result = pd.concat([df1, df4], axis=1, join='inner')
In [9]: result
    A   B   C   D   B   D   F
2  A2  B2  C2  D2  B2  D2  F2
3  A3  B3  C3  D3  B3  D3  F3

Hàm append

Hàm append là phiên bản đơn giản của hàm concat với axis=0

DataFrame.append(otherignore_index=Falseverify_integrity=False, sort=None)

Tham số thường dùng:

  • other: danh sách object DataFrame hoặc Series/Dictionary
  • ignore_index: boolean (mặc định False), giá trị True: giá trị index sẽ không được sử dụng
In [10]: df1.append(df4, ignore_index=True)
     A   B    C   D    F
0   A0  B0   C0  D0  NaN
1   A1  B1   C1  D1  NaN
2   A2  B2   C2  D2  NaN
3   A3  B3   C3  D3  NaN
4  NaN  B2  NaN  D2   F2
5  NaN  B3  NaN  D3   F3
6  NaN  B6  NaN  D6   F6
7  NaN  B7  NaN  D7   F7

Chú ý: hàm concat và append đều không thay đổi df1, mà chỉ tạo ra bản copy với phần df4 đã được nối

Append row vào DataFrame

Kiểu Series/Dictionary:

In [11]: s2 = pd.Series(['X0', 'X1', 'X2', 'X3'], index=['A', 'B', 'C', 'D'])
In [12]: df1.append(s2, ignore_index=True)
    A   B   C   D
0  A0  B0  C0  D0
1  A1  B1  C1  D1
2  A2  B2  C2  D2
3  A3  B3  C3  D3
4  X0  X1  X2  X3

Kiểu Dictionary:

In [13]: dicts = [{'A': 1, 'B': 2, 'C': 3, 'X': 4},
   ....:          {'A': 5, 'B': 6, 'C': 7, 'Y': 8}]
   ....: 
In [14]: df1.append(dicts, ignore_index=True)
    A   B   C    D    X    Y
0  A0  B0  C0   D0  NaN  NaN
1  A1  B1  C1   D1  NaN  NaN
2  A2  B2  C2   D2  NaN  NaN
3  A3  B3  C3   D3  NaN  NaN
4   1   2   3  NaN  4.0  NaN
5   5   6   7  NaN  NaN  8.0

SQL-like merging/joining

Hàm merge ở đây tương tự như query trong SQL database, DataFrame object tương tự như bảng trong SQL database.

Pandas cung cấp phép toán join đầy đủ các tính năng, hiệu suất cao. Những phương thức này có hiệu suất tốt hơn so với các open source khác (chẳng hạn base::merge.data.frame trong ngôn ngữ R)

pandas.merge(leftrighthow=’inner’on=Noneleft_on=Noneright_on=Noneleft_index=Falseright_index=Falsesort=Falsesuffixes=(‘_x’‘_y’)copy=Trueindicator=Falsevalidate=None)

Tham số thường dùng:

  • left: DataFrame
  • right: DataFrame
  • how: {‘left’, ‘right’, ‘outer’, ‘inner’}, mặc định ‘inner’
  • on: tên các cột hoặc index level để join. (chú ý: phải có ở cả 2 DataFrames)
  • left_on: tên các cột hoặc index level của DataFrame bên trái để join
  • right_on: tên các cột hoặc index level của DataFrame bên phải để join
  • sort: boolean, mặc định là False
  • indicator: boolean hoặc string, mặc định là False, nếu để True: sẽ có thêm một cột “_merge”(tên cột sẽ thay đổi theo string nếu được truyền vào) với thông tin source của từng row
    • left_only: key chỉ xuất hiện ở DataFrame bên trái
    • right_only: key chỉ xuất hiện ở DataFrame bên phải
    • both: key xuất hiện ở cả 2 DataFrame
  • validate: string, mặc định là None.
    • “one_to_one” or “1:1”: kiểm tra liệu key hợp nhất có riêng biệt ở cả 2 DataFrame không
    • “one_to_many” or “1:m”: kiểm tra liệu key hợp nhất có riêng biệt ở DataFrame bên trái không
    • “many_to_one” or “m:1”: kiểm tra liệu key hợp nhất có riêng biệt ở DataFrame bên phải không
    • “many_to_many” or “m:m”: được phép nhưng không đưa ra kết quả

Giá trị tham số “how” và giá trị join SQL tương đương:

Merge method SQL Join Name Description _merge
left LEFT OUTER JOIN Chỉ sử dụng keys của frame bên trái left_only
right RIGHT OUTER JOIN Chỉ sử dụng keys của frame bên phải right_only
outer FULL OUTER JOIN Sử dụng từng keys của 2 frames both
inner INNER JOIN Sử dụng keys giao nhau của 2 frames both
In [15]: left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
   ....:                      'key2': ['K0', 'K1', 'K0', 'K1'],
   ....:                      'A': ['A0', 'A1', 'A2', 'A3'],
   ....:                      'B': ['B0', 'B1', 'B2', 'B3']})
   ....: 

In [16]: right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
   ....:                       'key2': ['K0', 'K0', 'K0', 'K0'],
   ....:                       'C': ['C0', 'C1', 'C2', 'C3'],
   ....:                       'D': ['D0', 'D1', 'D2', 'D3']})
   ....: 

In [17]: pd.merge(left, right, on=['key1', 'key2'])
  key1 key2   A   B   C   D
0   K0   K0  A0  B0  C0  D0
1   K1   K0  A2  B2  C1  D1
2   K1   K0  A2  B2  C2  D2

In [18]: pd.merge(left, right, how='left', on=['key1', 'key2'])
  key1 key2   A   B    C    D
0   K0   K0  A0  B0   C0   D0
1   K0   K1  A1  B1  NaN  NaN
2   K1   K0  A2  B2   C1   D1
3   K1   K0  A2  B2   C2   D2
4   K2   K1  A3  B3  NaN  NaN

In [19]: pd.merge(left, right, how='right', on=['key1', 'key2'])
  key1 key2    A    B   C   D
0   K0   K0   A0   B0  C0  D0
1   K1   K0   A2   B2  C1  D1
2   K1   K0   A2   B2  C2  D2
3   K2   K0  NaN  NaN  C3  D3

In [20]: pd.merge(left, right, how='outer', on=['key1', 'key2'])
  key1 key2    A    B    C    D
0   K0   K0   A0   B0   C0   D0
1   K0   K1   A1   B1  NaN  NaN
2   K1   K0   A2   B2   C1   D1
3   K1   K0   A2   B2   C2   D2
4   K2   K1   A3   B3  NaN  NaN
5   K2   K0  NaN  NaN   C3   D3

Tham số “indicator”

In [21]: pd.merge(left, right, on=['key1','key2'], how='outer', indicator=True)
  key1 key2    A    B    C    D      _merge
0   K0   K0   A0   B0   C0   D0        both
1   K0   K1   A1   B1  NaN  NaN   left_only
2   K1   K0   A2   B2   C1   D1        both
3   K1   K0   A2   B2   C2   D2        both
4   K2   K1   A3   B3  NaN  NaN   left_only
5   K2   K0  NaN  NaN   C3   D3  right_only

Tham số “validate”

Pandas cung cấp tham số “validate” để kiểm tra liệu có sự trùng lặp key hợp nhất không. Tính duy nhất của khóa được kiểm tra trước khi merge, do đó sẽ chống việc tràn bộ nhớ, đồng thời đó cũng là một cách tốt để đảm bảo cấu trúc dữ liệu được như mong đợi. Nếu “validate” đúng thì kết quả sẽ được trả về, nếu không sẽ có lỗi được thông báo.

Ví dụ khi thực hiện merge như bên dưới với DataFrame B có giá trị trùng lặp thì sẽ trả ra lỗi.

In [22]: left = pd.DataFrame({'A' : [1,2], 'B' : [1, 2]})
In [23]: right = pd.DataFrame({'A' : [4,5,6], 'B': [2, 2, 2]}) 
In [24]: left
   A  B
0  1  1
1  2  2
In [25]: right
   A  B
0  4  2
1  5  2
2  6  2
In [26]: result = pd.merge(left, right, on='B', how='outer', validate="one_to_one")
...
MergeError: Merge keys are not unique in right dataset; not a one-to-one merge

 

Tài liệu tham khảo:

Add a Comment