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:
import numpy as np import pandas as pd vndata_raw = pd.read_excel('./dansovietnam.xlsx') print(vndata_raw)
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:
vndata = vndata_raw[['Unnamed: 0', '2017', '2018', '2019 (*)', 'Unnamed: 2', 'Unnamed: 4', 'Unnamed: 6']] print(vndata)
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:
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())
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ý:
vndata.describe()
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:
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)
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:
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:
print(vndata.describe())
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:
# Đặ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)")
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:
vungkinhte = pd.read_excel('./vungkinhte.xlsx') vungkinhte.columns = pd.MultiIndex.from_product([vungkinhte.columns, ['']]) data = pd.merge(vungkinhte, vndata) print(data)
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:
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'], 'value': range(6)}, columns=['key', 'value']) print(df)
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):
df_group = df.groupby('key') print(df_group)
<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:
print(df_group.sum()) # Tính tổng cột value có key giống nhau
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:
print(data.groupby('Vùng').sum())
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ụ:
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)
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:
# 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)
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:
# 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)
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:
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)
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:
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))
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:
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))
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:
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))
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:
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))
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:
print(data.groupby('Vùng').mean())
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:
print(Zaidap.com_views.groupby('category').apply(lambda x: x.describe()))
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:
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))
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é.