PANDAS - Thống kê và phân nhóm dữ liệu
Thống kê và phân nhóm dữ liệu trong 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 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: \n', df)
print('\nDescribe: \n', 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): \n', vndata['Diện tích(Km2)'].dtypes)
print('\nKiểu dữ liệu cột Dân số trung bình (Nghìn người): \n', 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]
key value
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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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: \n", df)
for key, value in df.groupby('key'):
print("\nkey: ", key)
print("value: \n", 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: \n", df)
agg = df.groupby('key').aggregate(['min', np.median, max])
print("\nagg: \n", 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 Freetuts 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]
hiepsiits_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("Hiepsiit views theo category: \n", hiepsiits_views )
print("\Hiepsiit views sau khi đã transform: \n", hiepsiits_views .groupby('category').transform(abc))
Out[19]
Hiepsiit 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
Hiepsiit 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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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: \n", hiepsiits_views.groupby('category').sum())
print("\nCác bài viết theo từng chuyên mục có tổng views > 5000: \n", hiepsiits_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 freetuts_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(freetuts_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