06/04/2021, 14:46

hống kê và phân nhóm dữ liệu trong Pandas - Pandas

Một trong những phần thiết yếu của xử lý dữ liệu lớn đó là thống kê dữ liệu. Như trong bài Xác suất và Thống kê với NumPy, mình đã đề cập đến khá nhiều phương thức thống kê như max, min, median, mean,.... Về cơ bản, thống kê các giá trị này cho ta 1 con số phản ánh cho một vấn đề nhất định trong ...

Một trong những phần thiết yếu của xử lý dữ liệu lớn đó là thống kê dữ liệu. Như trong bài Xác suất và Thống kê với NumPy, mình đã đề cập đến khá nhiều phương thức thống kê như max, min, median, mean,....

Về cơ bản, thống kê các giá trị này cho ta 1 con số phản ánh cho một vấn đề nhất định trong tập dữ liệu, chẳng hạn như lượt xem trung bình của series Pandas, bài có lượt view cao nhất, thấp nhất,..., dựa vào những con số này thì ta sẽ có một cái nhìn tổng quát về series Pandas so với các series khác và cải thiện nó nếu cần.

Trong bài này, chúng ta sẽ tìm hiểu cách để thống kê & phân nhóm dữ liệu trên Pandas từ những phương thức đơn giản như những gì đã học cho đến những khái niệm phức tạp hơn.

Dữ liệu mà chúng ta sử dụng để thao tác trong bài này sẽ là dữ liệu về dân số và diện tích các tỉnh thành trên Việt Nam trong 3 năm 2018, 2019, 2020. Các bạn có thể tải xuống ở đây và đây nhé.

Đầu tiên chúng ta sẽ import các thư viện cần thiết cũng như dữ liệu vào notebook:

In[1]
import numpy as np
import pandas as pd

vndata_raw = pd.read_excel('./dansovietnam.xlsx')

print(vndata_raw)
Out[1]
    Unnamed: 0            2017                       Unnamed: 2  
0          NaN  Diện tích(Km2)  Dân số trung bình (Nghìn người)   
1       Hà Nội          3358.6                           7420.1   
2    Vĩnh Phúc          1235.2                           1079.5   
3     Bắc Ninh           822.7                           1215.2   
4   Quảng Ninh          6177.8                           1243.6   
..         ...             ...                              ...   
59     Cần Thơ            1439                           1272.8   
60   Hậu Giang          1621.7                            774.6   
61   Sóc Trăng          3311.9                           1314.3   
62    Bạc Liêu            2669                            894.3   
63      Cà Mau          5221.2                           1226.3   

              2018                       Unnamed: 4        2019 (*)  
0   Diện tích(Km2)  Dân số trung bình (Nghìn người)  Diện tích(Km2)   
1           3358.6                           7520.7          3358.6   
2           1235.2                           1092.4          1235.9   
3            822.7                           1247.5           822.7   
4           6178.2                           1266.5          6178.2   
..             ...                              ...             ...   
59            1439                           1282.3            1439   
60          1621.7                            776.7          1621.7   
61          3311.9                           1315.9          3311.9   
62            2669                              897            2669   
63          5221.2                           1229.6          5221.2   

                         Unnamed: 6  
0   Dân số trung bình (Nghìn người)  
1                            8093.9  
2                            1154.8  
3                            1378.6  
4                            1324.8  
..                              ...  
59                             1236  
60                            732.2  
61                           1199.5  
62                            908.2  
63                           1194.3  

[64 rows x 7 columns]

Do đây là dữ liệu thô nên trước hết ta cần sắp xếp lại các cột cho logic hơn để thuận tiện cho việc tính toán. Ý tưởng ở đây sẽ là dùng MultiIndex để gom 2 phần Diện tích và Dân số trung bình lại với nhau.

Đầu tiên ta sắp xếp lại thứ tự các cột như sau:

In[2]
vndata = vndata_raw[['Unnamed: 0', '2017', '2018', '2019 (*)',  'Unnamed: 2', 'Unnamed: 4', 'Unnamed: 6']]

print(vndata)
Out[2]
    Unnamed: 0            2017            2018        2019 (*)  
0          NaN  Diện tích(Km2)  Diện tích(Km2)  Diện tích(Km2)   
1       Hà Nội          3358.6          3358.6          3358.6   
2    Vĩnh Phúc          1235.2          1235.2          1235.9   
3     Bắc Ninh           822.7           822.7           822.7   
4   Quảng Ninh          6177.8          6178.2          6178.2   
..         ...             ...             ...             ...   
59     Cần Thơ            1439            1439            1439   
60   Hậu Giang          1621.7          1621.7          1621.7   
61   Sóc Trăng          3311.9          3311.9          3311.9   
62    Bạc Liêu            2669            2669            2669   
63      Cà Mau          5221.2          5221.2          5221.2   

                         Unnamed: 2                       Unnamed: 4  
0   Dân số trung bình (Nghìn người)  Dân số trung bình (Nghìn người)   
1                            7420.1                           7520.7   
2                            1079.5                           1092.4   
3                            1215.2                           1247.5   
4                            1243.6                           1266.5   
..                              ...                              ...   
59                           1272.8                           1282.3   
60                            774.6                            776.7   
61                           1314.3                           1315.9   
62                            894.3                              897   
63                           1226.3                           1229.6   

                         Unnamed: 6  
0   Dân số trung bình (Nghìn người)  
1                            8093.9  
2                            1154.8  
3                            1378.6  
4                            1324.8  
..                              ...  
59                             1236  
60                            732.2  
61                           1199.5  
62                            908.2  
63                           1194.3  

[64 rows x 7 columns]

Sau khi đã sắp xếp xong, ta sẽ dùng MultiIndex để gom Diện tích và Dân số trung bình lại với nhau:

In[3]
columns = pd.MultiIndex.from_tuples([
       ('Tỉnh', ''),
       ('Diện tích(Km2)', 2017), ('Diện tích(Km2)', 2018),
       ('Diện tích(Km2)', 2019),
       ('Dân số trung bình (Nghìn người)', 2017),
       ('Dân số trung bình (Nghìn người)', 2018),
       ('Dân số trung bình (Nghìn người)', 2019)])
vndata.columns = columns
vndata = vndata.iloc[1:].reset_index(drop=True) # Reset index và xoá hàng đầu tiên
print(vndata.head())
Out[3]
         Tỉnh Diện tích(Km2)                 Dân số trung bình (Nghìn người)  
                        2017    2018    2019                            2017   
0      Hà Nội         3358.6  3358.6  3358.6                          7420.1   
1   Vĩnh Phúc         1235.2  1235.2  1235.9                          1079.5   
2    Bắc Ninh          822.7   822.7   822.7                          1215.2   
3  Quảng Ninh         6177.8  6178.2  6178.2                          1243.6   
4   Hải Dương         1668.2  1668.2  1668.2                          1797.3   

                   
     2018    2019  
0  7520.7  8093.9  
1  1092.4  1154.8  
2  1247.5  1378.6  
3  1266.5  1324.8  
4  1807.5  1896.9  

Vậy là chúng ta đã có 1 DataFrame với cấu trúc khá logic, giờ ta cùng làm việc với tập dữ liệu này nhé.

1. Thống kê dữ liệu

Trong bài Xác suất và Thống kê với NumPy, mình đã giới thiệu về các hàm thống kê như mean, median,... và ý nghĩa toán học của chúng. Với Pandas nhìn chung không khác so với NumPy, ngoài ra Pandas còn có một phương thức khá hữu ích là describe, phương thức này sẽ trả về một bảng giá trị chứa một số giá trị thống kê trên tập dữ liệu như max, min, std, mean,..., ví dụ:

df = pd.DataFrame({'A': np.random.randn(5), 'B': np.random.randn(5), 'C': np.random.randn(5)})

print('Df: 
', df)

print('
Describe: 
', df.describe())
Df: 
           A         B         C
0 -1.656510  1.694034 -1.361904
1 -0.805627  1.260333  1.108379
2  1.119366  0.783096  0.605045
3 -1.657691 -0.809321 -0.126095
4  0.681620  0.605291 -0.525218

Describe: 
               A         B         C
count  5.000000  5.000000  5.000000
mean  -0.463768  0.706686 -0.059959
std    1.302227  0.948127  0.964772
min   -1.657691 -0.809321 -1.361904
25%   -1.656510  0.605291 -0.525218
50%   -0.805627  0.783096 -0.126095
75%    0.681620  1.260333  0.605045
max    1.119366  1.694034  1.108379

Ta sẽ thử phương thức này lên dữ liệu ta đang xử lý:

In[4]
vndata.describe()
Out[4]
            Tỉnh Diện tích(Km2)                  
                           2017    2018    2019   
count         63           63.0    63.0    63.0   
unique        63           63.0    63.0    63.0   
top     Bạc Liêu         4860.0  4860.0  4860.0   
freq           1            1.0     1.0     1.0   

       Dân số trung bình (Nghìn người)                  
                                  2017    2018    2019  
count                             63.0    63.0    63.0  
unique                            63.0    63.0    63.0  
top                             1791.5  1919.2  1022.6  
freq                               1.0     1.0     1.0  

Ta thấy output là những giá trị khác hẳn với trên kia và khá là ... vô nghĩa. Lý do là do kiểu dữ liệu ở 2 cột Dân số và Diện tích đang là object:

In[5]
print('Kiểu dữ liệu cột Diện tích(Km2): 
', vndata['Diện tích(Km2)'].dtypes)
print('
Kiểu dữ liệu cột Dân số trung bình (Nghìn người): 
', vndata['Dân số trung bình (Nghìn người)'].dtypes)
Out[5]
Kiểu dữ liệu cột Diện tích(Km2): 
2017    object
2018    object
2019    object
dtype: object

Kiểu dữ liệu cột Dân số trung bình (Nghìn người): 
2017    object
2018    object
2019    object
dtype: object

Để xử lý vấn đề này ta sẽ đổi kiểu dữ liệu sang số thực (float) như sau:

In[6]
vndata['Diện tích(Km2)'] = vndata['Diện tích(Km2)'].astype(np.float64)
vndata['Dân số trung bình (Nghìn người)'] = vndata['Dân số trung bình (Nghìn người)'].astype(np.float64)

Và ta sẽ thử describe lại:

In[7]
print(vndata.describe())
Out[7]
      Diện tích(Km2)                              
                2017          2018          2019   
count      63.000000     63.000000     63.000000   
mean     5253.196825   5253.258730   5253.266667   
std      3674.519838   3674.509463   3674.486135   
min       822.700000    822.700000    822.700000   
25%      2376.500000   2376.550000   2376.550000   
50%      4621.700000   4621.700000   4621.700000   
75%      6788.550000   6788.550000   6788.550000   
max     16481.600000  16481.600000  16481.400000   

      Dân số trung bình (Nghìn người)                            
                                 2017         2018         2019  
count                       63.000000    63.000000    63.000000  
mean                      1486.850794  1502.634921  1531.492063  
std                       1329.271815  1350.234597  1436.123967  
min                        323.200000   327.900000   314.400000  
25%                        860.650000   867.050000   865.650000  
50%                       1226.300000  1239.200000  1232.300000  
75%                       1601.700000  1613.300000  1646.950000  
max                       8444.600000  8598.700000  9038.600000  

Sử dụng phương thức describe là một trong những cách khá hữu ích khi chúng ta mới bắt đầu xử lý tập dữ liệu của mình. Nó cho ta một cái nhìn chung và tổng quát về dữ liệu, chẳng hạn dân số trung bình của các tỉnh đều tăng từng năm, tỉnh có dân số lớn nhất tăng đều và tăng mạnh từ 2018 => 2019, trong khi tỉnh có dân số thấp nhất thì lại giảm từ 2018 => 2019,...

Ta có một số ví dụ với các phương thức trên:

In[8]
# Đặt 2 biến tên cột để tái sử dụng cho nhiều lần index 
area_key = 'Diện tích(Km2)'
population_key = 'Dân số trung bình (Nghìn người)'

area = vndata[area_key]
population = vndata[population_key]

print("Tổng dân số Việt Nam năm 2018: ", population[2019].sum() * 1000, "(người)")

print("Diện tích của Việt Nam năm 2017: ", area[2019].sum(), '(km2)')

print("Mật độ dân số trung bình / km2 năm 2019: ", population[2019].sum() * 1000 / area[2019].sum())

# Lấy index, tên và giá trị của tỉnh / thành phố có diện tích lớn nhất
largest_unit_by_area_index = area.loc[area[2019] == area[2019].max()].index.values[0]
largest_unit_by_area_name = vndata['Tỉnh'][largest_unit_by_area_index]
largest_unit_by_area_value = vndata.iloc[largest_unit_by_area_index][(area_key, 2019)]

print("Tỉnh / Thành phố có diện tích lớn nhất Việt Nam: ",  largest_unit_by_area_name, "(", largest_unit_by_area_value, "km2)")

# Lấy index của tỉnh / thành phố có dân số lớn nhất Việt Nam năm 2019

largest_unit_by_population_index = population.loc[population[2019] == population[2019].max()].index.values[0]
largest_unit_by_population_name = vndata['Tỉnh'][largest_unit_by_population_index]
largest_unit_by_population_value = vndata.iloc[largest_unit_by_population_index][(population_key, 2019)] * 1000

print("Tỉnh / Thành phố có dân số lớn nhất trong năm 2019: ", largest_unit_by_population_name, "(", largest_unit_by_population_value, "người)")
Out[8]
Tổng dân số Việt Nam năm 2018:  96483999.99999997 (người)
Diện tích của Việt Nam năm 2017:  330955.80000000005 (km2)
Mật độ dân số trung bình / km2 năm 2019:  291.531376697432
Tỉnh / Thành phố có diện tích lớn nhất Việt Nam:  Nghệ An ( 16481.4 km2)
Tỉnh / Thành phố có dân số lớn nhất trong năm 2019:  TP.Hồ Chí Minh ( 9038600.0 người)

2. Phân nhóm dữ liệu

Các phương thức trên rất hữu ích và đều áp dụng được cho cả Series lẫn DataFrame, tuy nhiên đó vẫn chỉ là các phương thức khá đơn giản. Để đào sâu vào dữ liệu, ta cần tính toán trên nhiều cụm dữ liệu con trong tập dữ liệu trong khi các phương thức như sum, count,... chỉ áp dụng cho toàn bộ tập dữ liệu.

Nếu sử dụng các phương thức trên thì code sẽ rất dài, gây tốn bộ nhớ và dễ xảy ra bug. Pandas hỗ trợ việc phân nhóm dữ liệu bằng phương thức groupby, nó cho phép ta phân cụm và thao tác trên dữ liệu một cách nhanh chóng theo nhãn hoặc index tương ứng.

Trước khi đi sâu vào các thao tác với groupby, chúng ta sẽ bổ sung thêm 1 cột dữ liệu là "Vùng kinh tế" của các tỉnh vào tập dữ liệu sẵn có của ta bằng dữ liệu vùng kinh tế ở bên dưới như sau:

In[9]
vungkinhte = pd.read_excel('./vungkinhte.xlsx')

vungkinhte.columns = pd.MultiIndex.from_product([vungkinhte.columns, ['']])

data = pd.merge(vungkinhte, vndata)

print(data)
Out[9]
           Tỉnh                           Vùng Diện tích(Km2)                  
                                                         2017    2018    2019   
0      Hà Giang  Trung du và miền núi phía Bắc         7929.5  7929.5  7929.5   
1      Cao Bằng  Trung du và miền núi phía Bắc         6700.3  6700.3  6700.3   
2       Bắc Kạn  Trung du và miền núi phía Bắc         4860.0  4860.0  4860.0   
3   Tuyên Quang  Trung du và miền núi phía Bắc         5867.9  5867.9  5867.9   
4       Lào Cai  Trung du và miền núi phía Bắc         6364.0  6364.0  6364.0   
..          ...                            ...            ...     ...     ...   
58      Cần Thơ        Đồng bằng sông Cửu Long         1439.0  1439.0  1439.0   
59    Hậu Giang        Đồng bằng sông Cửu Long         1621.7  1621.7  1621.7   
60    Sóc Trăng        Đồng bằng sông Cửu Long         3311.9  3311.9  3311.9   
61     Bạc Liêu        Đồng bằng sông Cửu Long         2669.0  2669.0  2669.0   
62       Cà Mau        Đồng bằng sông Cửu Long         5221.2  5221.2  5221.2   

   Dân số trung bình (Nghìn người)                  
                              2017    2018    2019  
0                            833.5   846.5   858.1  
1                            535.4   540.4   530.9  
2                            323.2   327.9   314.4  
3                            773.5   780.1   786.3  
4                            694.4   705.6   733.3  
..                             ...     ...     ...  
58                          1272.8  1282.3  1236.0  
59                           774.6   776.7   732.2  
60                          1314.3  1315.9  1199.5  
61                           894.3   897.0   908.2  
62                          1226.3  1229.6  1194.3  

[63 rows x 8 columns]

Giới thiệu về nguyên tắc split-apply-combine

Phương thức groupby hoạt động theo nguyên tắc split-apply-combine như sau:

  • Chia (spliting) dữ liệu thành các nhóm ra dựa trên các điều kiện cho sẵn.
  • Áp dụng (applying) các hàm lên từng nhóm cụ thể.
  • Kết hợp (combining) kết quả lại thành một cấu trúc dữ liệu.

Giả sử ta có DataFrame sau:

In[10]
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'], 'value': range(6)}, columns=['key', 'value'])
print(df)
Out[10]
  key  value
0   A      0
1   B      1
2   C      2
3   A      3
4   B      4
5   C      5

Giờ ta muốn nhóm DataFrame trên dựa vào cột key (các hàng có giá trị ở cột key tương ứng giống nhau thì được gom lại):

In[11]
df_group = df.groupby('key')

print(df_group)
Out[11]
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000164865C84F0>

Một điều ta có thể thấy là giá trị trả về sau khi sử dụng phương thức groupby không phải là một object DataFrame mà là một kiểu object mới là DataFrameGroupBy.

Đây là một điểm vô cùng thú vị trong Pandas: object này sẵn sàng làm các thao tác tính toán, nhưng nó sẽ không làm cho đến khi ta áp dụng một phương thức cụ thể nào đó lên object (được gọi là "lazy evaluation", cũng gần giống như lazy loading trong lập trình web vậy, trang web sẽ không load ảnh cho đến khi user scroll đến phần đó).

Để trả về một giá trị cụ thể, ta sẽ áp một phương thức thống kê bất kỳ lên object này (đây chính là bước applying đã nói bên trên), chẳng hạn:

In[12]
print(df_group.sum()) # Tính tổng cột value có key giống nhau
Out[12]
     value
key       
A        3
B        5
C        7

Ta có thể áp dụng với tập dữ liệu được bổ sung ở bên trên:

In[13]
print(data.groupby('Vùng').sum())
Out[14]
                                     Diện tích(Km2)                    
                                               2017     2018     2019   
Vùng                                                                    
Bắc Trung Bộ và Duyên Hải miền Trung        95649.9  95653.0  95652.8   
Trung du và miền núi phía Bắc               95200.4  95200.4  95200.3   
Tây Nguyên                                  54508.3  54508.3  54508.3   
Đông Nam Bộ                                 23518.5  23518.7  23518.7   
Đồng bằng sông Cửu Long                     40816.3  40816.4  40816.4   
Đồng bằng sông Hồng                         21258.0  21258.5  21259.3   

                                     Dân số trung bình (Nghìn người)           
                                                                2017     2018   
Vùng                                                                            
Bắc Trung Bộ và Duyên Hải miền Trung                         19924.5  20056.9   
Trung du và miền núi phía Bắc                                12148.9  12292.7   
Tây Nguyên                                                    5778.5   5871.0   
Đông Nam Bộ                                                  16739.6  17074.3   
Đồng bằng sông Cửu Long                                      17738.0  17804.7   
Đồng bằng sông Hồng                                          21342.1  21566.4   

                                               
                                         2019  
Vùng                                           
Bắc Trung Bộ và Duyên Hải miền Trung  20220.4  
Trung du và miền núi phía Bắc         12569.3  
Tây Nguyên                             5861.3  
Đông Nam Bộ                           17930.3  
Đồng bằng sông Cửu Long               17282.5  
Đồng bằng sông Hồng                   22620.2  

Ta có thể thấy vùng Bắc Trung Bộ và Duyên Hải miền Trung có diện tích lớn nhất lẫn dân số đông nhất trong 6 vùng kinh tế - xã hội của cả nước.

Các tính năng cơ bản

Lặp qua các nhóm

Lặp qua các nhóm dữ liệu trong GroupBy object khá là đơn giản, ví dụ:

df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],
                   'value1': np.random.randint(10, 100, 6), 'value2': np.random.randint(0, 10, 6)},
                  columns = ['key', 'value1', 'value2'])

print("df: 
", df)

for key, value in df.groupby('key'):
    print("
key: ", key)
    print("value: 
", value)
df: 
   key  value1  value2
0   A      31       6
1   B      52       5
2   C      92       4
3   A      95       5
4   B      30       9
5   C      35       6

key:  A
value: 
   key  value1  value2
0   A      31       6
3   A      95       5

key:  B
value: 
   key  value1  value2
1   B      52       5
4   B      30       9

key:  C
value: 
   key  value1  value2
2   C      92       4
5   C      35       6

Chọn các nhóm cụ thể

Để chọn từng nhóm cụ thể trong GroupBy object, ta có thể sử dụng phương thức get_group như sau:

print(df.groupby('key').get_group('A'))
  key  value1  value2
0   A      31       6
3   A      95       5

Các phương thức chính của GroupBy

GroupBy object là một abstraction rất linh hoạt, bạn có thể xem nó như một tập hợp của DataFrame vậy. Có 4 phương thức quan trọng nhất trên object này, đó là: aggregate (tổng hợp), filter (lọc), transform (biến đổi) và apply (áp dụng).

Trước khi tìm hiểu 4 phương thức trên, ta sẽ làm quen với một số tính năng cơ bản của GroupBy nhé.

Aggregate

Chúng ta đã làm quen với nhiều phương thức như mean, median, sum,... giúp tổng hợp và thống kê dữ liệu. Với aggregate thì ta có thể làm nhiều hơn thế.

Phương thức này có thể lấy một chuỗi, một hàm hoặc một danh sách của chúng và tính tất cả cùng một lúc. Ví dụ:

In[15]
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],
                   'value1': np.random.randint(10, 100, 6), 'value2': np.random.randint(0, 10, 6)},
                  columns = ['key', 'value1', 'value2'])

print("df: 
", df)

agg = df.groupby('key').aggregate(['min', np.median, max])
print("
agg: 
", agg)
Out[15]
df: 
   key  value1  value2
0   A      93       1
1   B      59       4
2   C      64       1
3   A      76       9
4   B      15       5
5   C      69       5

agg: 
     value1            value2           
       min median max    min median max
key                                    
A       76   84.5  93      1    5.0   9
B       15   37.0  59      4    4.5   5
C       64   66.5  69      1    3.0   5

Ta có thể sử dụng phương thức rút gọn là agg như sau:

In[16]
# Diện tích lớn nhất và nhỏ nhất của các tỉnh
minmax_area = data.drop(labels='Tỉnh', axis=1).groupby('Vùng').agg([max, min])['Diện tích(Km2)']

print(minmax_area)
Out[16]
                                         2017             2018          
                                          max     min      max     min   
Vùng                                                                     
Bắc Trung Bộ và Duyên Hải miền Trung  16481.6  1284.9  16481.6  1284.9   
Trung du và miền núi phía Bắc         14123.5  3526.6  14123.5  3526.6   
Tây Nguyên                            15511.0  6509.3  15511.0  6509.3   
Đông Nam Bộ                            6876.8  1981.0   6876.8  1981.0   
Đồng bằng sông Cửu Long                6348.8  1439.0   6348.8  1439.0   
Đồng bằng sông Hồng                    6177.8   822.7   6178.2   822.7   

                                         2019          
                                          max     min  
Vùng                                                   
Bắc Trung Bộ và Duyên Hải miền Trung  16481.4  1284.9  
Trung du và miền núi phía Bắc         14123.5  3526.6  
Tây Nguyên                            15511.0  6509.3  
Đông Nam Bộ                            6876.8  1981.0  
Đồng bằng sông Cửu Long                6348.8  1439.0  
Đồng bằng sông Hồng                    6178.2   822.7  

Ngoài việc truyền tham số là một mảng chứa các phương thức, ta có thể chỉ định cho từng cột các phương thức riêng như sau:

In[17]
# Chỉ định cột dân số 2017 là min, 2018 là max còn 2019 là mean
minmaxmean_pop = data.drop(labels='Tỉnh', axis=1).groupby('Vùng').agg({(population_key, 2017): 'min', (population_key, 2018): 'max', (population_key, 2019): 'mean'})

print(minmaxmean_pop)
Out[17]
                                     Dân số trung bình (Nghìn người)          
                                                                2017    2018   
Vùng                                                                           
Bắc Trung Bộ và Duyên Hải miền Trung                           607.0  3558.2   
Trung du và miền núi phía Bắc                                  323.2  1691.8   
Tây Nguyên                                                     520.0  1919.2   
Đông Nam Bộ                                                    968.9  8598.7   
Đồng bằng sông Cửu Long                                        774.6  2164.2   
Đồng bằng sông Hồng                                            805.7  7520.7   

                                                   
                                             2019  
Vùng                                               
Bắc Trung Bộ và Duyên Hải miền Trung  1444.314286  
Trung du và miền núi phía Bắc          897.807143  
Tây Nguyên                            1172.260000  
Đông Nam Bộ                           2988.383333  
Đồng bằng sông Cửu Long               1329.423077  
Đồng bằng sông Hồng                   2056.381818  

Ta cũng có thể đặt tên lại cho các cột của ouput mà mình muốn:

In[18]
minmax_pop = data.drop(labels='Tỉnh', axis=1).groupby('Vùng').agg(**{'Dân số năm 2017 (tỉnh thấp nhất)': ((population_key, 2017), min), 'Dân số năm 2017 (tỉnh cao nhất)': ((population_key, 2017), max)})

print(minmax_pop)
Out[18]
                                      Dân số năm 2017 (tỉnh thấp nhất)  
Vùng                                                                     
Bắc Trung Bộ và Duyên Hải miền Trung                             607.0   
Trung du và miền núi phía Bắc                                    323.2   
Tây Nguyên                                                       520.0   
Đông Nam Bộ                                                      968.9   
Đồng bằng sông Cửu Long                                          774.6   
Đồng bằng sông Hồng                                              805.7   

                                      Dân số năm 2017 (tỉnh cao nhất)  
Vùng                                                                   
Bắc Trung Bộ và Duyên Hải miền Trung                           3544.4  
Trung du và miền núi phía Bắc                                  1674.4  
Tây Nguyên                                                     1896.6  
Đông Nam Bộ                                                    8444.6  
Đồng bằng sông Cửu Long                                        2161.7  
Đồng bằng sông Hồng                                            7420.1  

Transformation

Trong khi aggregate trả về dữ liệu là một giá trị cụ thể được tính toán theo hàm đã cho, thì transformation sẽ trả về toàn bộ đầy đủ dữ liệu sau khi đã được biến đổi (transform).

Giả sử ta có dữ liệu về lượt xem của một số chuyên mục trong Zaidap.com nhưng dữ liệu lại bị tính sai và ta cần +100 views ở từng mục thì ta có thể sử dụng transform như sau:

In[19]
Zaidap.com_views = pd.DataFrame({'posts': ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
                               'category': ['NumPy', 'Matplotlib', 'NumPy', 'Pandas', 'Pandas', 'NumPy', 'Pandas'], 
                               'views': [1333, 4334, 4211, 3244, 1132, 3415, 1324]})
def abc(x):
    return x + 100

print("Zaidap.com views theo category: 
", Zaidap.com_views)
print("
Zaidap.com views sau khi đã transform: 
", Zaidap.com_views.groupby('category').transform(abc))
Out[19]
Zaidap.com views theo category: 
   posts    category  views
0     A       NumPy   1333
1     B  Matplotlib   4334
2     C       NumPy   4211
3     D      Pandas   3244
4     E      Pandas   1132
5     F       NumPy   3415
6     G      Pandas   1324

Zaidap.com views sau khi đã transform: 
    views
0   1433
1   4434
2   4311
3   3344
4   1232
5   3515
6   1424

Với bộ dữ liệu về Việt Nam, để tính chênh lệch Dân số cũng như Diện tích của các Tỉnh / Thành phố theo từng năm so với giá trị trung bình của Vùng Kinh tế - Xã hội tương ứng trong năm đó thì ta cũng có thể dùng transform để giải quyết vấn đề này một cách nhanh chóng:

In[20]
m = data.drop(labels='Tỉnh', axis=1).groupby('Vùng').transform(lambda x: x - x.mean())
tinh = data['Tỉnh'].to_frame()
tinh.columns = [['Tỉnh'], ['']]

print(pd.merge(tinh, m, left_index=True, right_index=True))
Out[20]
           Tỉnh Diện tích(Km2)                            
                          2017         2018         2019   
0      Hà Giang    1129.471429  1129.471429  1129.478571   
1      Cao Bằng     -99.728571   -99.728571   -99.721429   
2       Bắc Kạn   -1940.028571 -1940.028571 -1940.021429   
3   Tuyên Quang    -932.128571  -932.128571  -932.121429   
4       Lào Cai    -436.028571  -436.028571  -436.021429   
..          ...            ...          ...          ...   
58      Cần Thơ   -1700.715385 -1700.723077 -1700.723077   
59    Hậu Giang   -1518.015385 -1518.023077 -1518.023077   
60    Sóc Trăng     172.184615   172.176923   172.176923   
61     Bạc Liêu    -470.715385  -470.723077  -470.723077   
62       Cà Mau    2081.484615  2081.476923  2081.476923   

   Dân số trung bình (Nghìn người)                          
                              2017        2018        2019  
0                       -34.278571  -31.550000  -39.707143  
1                      -332.378571 -337.650000 -366.907143  
2                      -544.578571 -550.150000 -583.407143  
3                       -94.278571  -97.950000 -111.507143  
4                      -173.378571 -172.450000 -164.507143  
..                             ...         ...         ...  
58                      -91.661538  -87.292308  -93.423077  
59                     -589.861538 -592.892308 -597.223077  
60                      -50.161538  -53.692308 -129.923077  
61                     -470.161538 -472.592308 -421.223077  
62                     -138.161538 -139.992308 -135.123077  

[63 rows x 7 columns]

Filter

Phương thức filter trả về một tập hợp con thoả mãn điều kiện mà ta đề ra. Chẳng hạn với ví dụ đầu tiên ở phương thức transform ta có danh sách các chuyên mục với số views tương đương, giả sử ta cần lọc ra các bài viết ở các chuyên mục có lượt views > 5000:

In[21]
def filter_func(x):
    return x['views'].sum() > 5000

print("Tổng số views theo từng chuyên mục: 
", Zaidap.com_views.groupby('category').sum())
print("
Các bài viết theo từng chuyên mục có tổng views > 5000: 
", Zaidap.com_views.groupby('category').filter(filter_func))
Out[21]
Tổng số views theo từng chuyên mục: 
             views
category         
Matplotlib   4334
NumPy        8959
Pandas       5700

# Matplotlib có tổng views < 5000 do đó các bài trong chuyên mục này sẽ bị loại
Các bài viết theo từng chuyên mục có tổng views > 5000: 
   posts category  views
0     A    NumPy   1333
2     C    NumPy   4211
3     D   Pandas   3244
4     E   Pandas   1132
5     F    NumPy   3415
6     G   Pandas   1324

Với dữ liệu về Việt Nam, giả sử ta cần tìm các Tỉnh / Thành phố nằm trong Vùng Kinh tế - Xã hội có số dân trung bình trên mỗi Tỉnh / Thành phố trên 2 triệu người thì ta có thể dùng filter để làm như sau:

In[22]
def filter_func(x):
    return x[('Dân số trung bình (Nghìn người)', 2019)].mean() > 2000

print(data.groupby('Vùng').filter(filter_func))
Out[22]
                 Tỉnh                 Vùng Diện tích(Km2)                  
                                                     2017    2018    2019   
14             Hà Nội  Đồng bằng sông Hồng         3358.6  3358.6  3358.6   
15         Quảng Ninh  Đồng bằng sông Hồng         6177.8  6178.2  6178.2   
16          Vĩnh Phúc  Đồng bằng sông Hồng         1235.2  1235.2  1235.9   
17           Bắc Ninh  Đồng bằng sông Hồng          822.7   822.7   822.7   
18          Hải Dương  Đồng bằng sông Hồng         1668.2  1668.2  1668.2   
19          Hải Phòng  Đồng bằng sông Hồng         1561.8  1561.8  1561.8   
20           Hưng Yên  Đồng bằng sông Hồng          930.2   930.2   930.2   
21          Thái Bình  Đồng bằng sông Hồng         1586.3  1586.4  1586.4   
22             Hà Nam  Đồng bằng sông Hồng          861.9   861.9   861.9   
23           Nam Định  Đồng bằng sông Hồng         1668.5  1668.5  1668.6   
24          Ninh Bình  Đồng bằng sông Hồng         1386.8  1386.8  1386.8   
44         Bình Phước          Đông Nam Bộ         6876.8  6876.8  6876.8   
45           Tây Ninh          Đông Nam Bộ         4041.3  4041.3  4041.3   
46         Bình Dương          Đông Nam Bộ         2694.6  2694.6  2694.6   
47           Đồng Nai          Đông Nam Bộ         5863.6  5863.6  5863.6   
48  Bà Rịa - Vũng Tàu          Đông Nam Bộ         1981.0  1981.0  1981.0   
49     TP.Hồ Chí Minh          Đông Nam Bộ         2061.2  2061.4  2061.4   

   Dân số trung bình (Nghìn người)                  
                              2017    2018    2019  
14                          7420.1  7520.7  8093.9  
15                          1243.6  1266.5  1324.8  
16                          1079.5  1092.4  1154.8  
17                          1215.2  1247.5  1378.6  
18                          1797.3  1807.5  1896.9  
19                          1997.7  2013.8  2033.3  
20                          1176.3  1188.9  1255.8  
21                          1791.5  1793.2  1862.2  
22                           805.7   808.2   854.5  
23                          1853.3  1854.4  1780.9  
24                           961.9   973.3   984.5  
44                           968.9   979.6   997.8  
45                          1126.2  1133.4  1171.7  
46                          2071.0  2163.6  2456.3  
47                          3027.3  3086.1  3113.7  
48                          1101.6  1112.9  1152.2  
49                          8444.6  8598.7  9038.6  

Như vậy ta có thể thấy chỉ có các Tỉnh / Thành phố thuộc Đông Nam Bộ và Đồng bằng sông Hồng là có số dân trung bình > 2 triệu người mỗi nơi. Ta có thể kiểm tra lại như sau:

In[23]
print(data.groupby('Vùng').mean())
Out[23]
                                     Diện tích(Km2)                
                                               2017          2018   
Vùng                                                                
Bắc Trung Bộ và Duyên Hải miền Trung    6832.135714   6832.357143   
Trung du và miền núi phía Bắc           6800.028571   6800.028571   
Tây Nguyên                             10901.660000  10901.660000   
Đông Nam Bộ                             3919.750000   3919.783333   
Đồng bằng sông Cửu Long                 3139.715385   3139.723077   
Đồng bằng sông Hồng                     1932.545455   1932.590909   

                                                    
                                              2019   
Vùng                                                 
Bắc Trung Bộ và Duyên Hải miền Trung   6832.342857   
Trung du và miền núi phía Bắc          6800.021429   
Tây Nguyên                            10901.660000   
Đông Nam Bộ                            3919.783333   
Đồng bằng sông Cửu Long                3139.723077   
Đồng bằng sông Hồng                    1932.663636   

                                     Dân số trung bình (Nghìn người)  
                                                                2017   
Vùng                                                                   
Bắc Trung Bộ và Duyên Hải miền Trung                     1423.178571   
Trung du và miền núi phía Bắc                             867.778571   
Tây Nguyên                                               1155.700000   
Đông Nam Bộ                                              2789.933333   
Đồng bằng sông Cửu Long                                  1364.461538   
Đồng bằng sông Hồng                                      1940.190909   

                                                                
                                             2018         2019  
Vùng                                                            
Bắc Trung Bộ và Duyên Hải miền Trung  1432.635714  1444.314286  
Trung du và miền núi phía Bắc          878.050000   897.807143  
Tây Nguyên                            1174.200000  1172.260000  
Đông Nam Bộ                           2845.716667  2988.383333  
Đồng bằng sông Cửu Long               1369.592308  1329.423077  
Đồng bằng sông Hồng                   1960.581818  2056.381818  

Apply

Phương thức apply cho phép bạn áp dụng một hàm tuỳ ý lên một object GroupBy, phương thức này có thể thay thế được cho cả agg lẫn transform và đôi khi là một số trường hợp đặc biệt khác.

Giả sử ta muốn mô tả dữ liệu bằng phương thức describe của từng chuyên mục trong DataFrame Zaidap.com_views thì ta có thể sử dụng phương thức này một cách rất đơn giản và hiệu quả như sau:

In[24]
print(Zaidap.com_views.groupby('category').apply(lambda x: x.describe()))
Out[24]
                        views
category                     
Matplotlib count     1.000000
           mean   4334.000000
           std            NaN
           min    4334.000000
           25%    4334.000000
           50%    4334.000000
           75%    4334.000000
           max    4334.000000
NumPy      count     3.000000
           mean   2986.333333
           std    1486.114845
           min    1333.000000
           25%    2374.000000
           50%    3415.000000
           75%    3813.000000
           max    4211.000000
Pandas     count     3.000000
           mean   1900.000000
           std    1167.890406
           min    1132.000000
           25%    1228.000000
           50%    1324.000000
           75%    2284.000000
           max    3244.000000

Nếu đặt Dân số năm 2019 làm mốc và ta muốn tính % dân số theo từng năm trong tập dữ liệu Việt Nam thì ta có thể sử dụng phương thức apply như sau:

In[25]
def norm(x):
    x[('Dân số trung bình (Nghìn người)', 2017)] /= x[('Dân số trung bình (Nghìn người)', 2019)]
    x[('Dân số trung bình (Nghìn người)', 2018)] /= x[('Dân số trung bình (Nghìn người)', 2019)]
    x[('Dân số trung bình (Nghìn người)', 2019)] = 1.0
    return x

print(data.groupby('Vùng').apply(norm))
Out[25]
           Tỉnh                           Vùng Diện tích(Km2)                  
                                                         2017    2018    2019   
0      Hà Giang  Trung du và miền núi phía Bắc         7929.5  7929.5  7929.5   
1      Cao Bằng  Trung du và miền núi phía Bắc         6700.3  6700.3  6700.3   
2       Bắc Kạn  Trung du và miền núi phía Bắc         4860.0  4860.0  4860.0   
3   Tuyên Quang  Trung du và miền núi phía Bắc         5867.9  5867.9  5867.9   
4       Lào Cai  Trung du và miền núi phía Bắc         6364.0  6364.0  6364.0   
..          ...                            ...            ...     ...     ...   
58      Cần Thơ        Đồng bằng sông Cửu Long         1439.0  1439.0  1439.0   
59    Hậu Giang        Đồng bằng sông Cửu Long         1621.7  1621.7  1621.7   
60    Sóc Trăng        Đồng bằng sông Cửu Long         3311.9  3311.9  3311.9   
61     Bạc Liêu        Đồng bằng sông Cửu Long         2669.0  2669.0  2669.0   
62       Cà Mau        Đồng bằng sông Cửu Long         5221.2  5221.2  5221.2   

   Dân số trung bình (Nghìn người)                 
                              2017      2018 2019  
0                         0.971332  0.986482  1.0  
1                         1.008476  1.017894  1.0  
2                         1.027990  1.042939  1.0  
3                         0.983721  0.992115  1.0  
4                         0.946952  0.962226  1.0  
..                             ...       ...  ...  
58                        1.029773  1.037460  1.0  
59                        1.057908  1.060776  1.0  
60                        1.095707  1.097040  1.0  
61                        0.984695  0.987668  1.0  
62                        1.026794  1.029557  1.0  

[63 rows x 8 columns]

3. Tổng kết

Vậy là chúng ta đã hoàn tất bài Thống kê và Phân nhóm dữ liệu với Pandas. Đây là một bài rất quan trọng, giúp ta biết cách phân nhóm dữ liệu dựa trên các key giống nhau một cách đơn giản cũng như các phương thức mạnh mẽ mà Pandas hỗ trợ (agg, transform,...). Trong bài tiếp theo chúng ta sẽ cùng tìm hiểu về Pivot Tables trong Pandas, hẹn gặp các bạn ở bài tiếp theo nhé.

0