Tự Học Data Science · 07/12/2023 0

03.07 Combining Datasets – Merge and Join

Relational Algebra

Hành vi được thực hiện trong pd.merge() là một phần của những gì được biết đến là hệ thống đại số quan hệ, đây là một tập hợp các quy tắc hình thức để xử lý dữ liệu quan hệ và tạo nền tảng khái niệm của các hoạt động có sẵn trong hầu hết các cơ sở dữ liệu.Sự mạnh mẽ của phương pháp đại số quan hệ là nó đề xuất một số hoạt động cơ bản, những hoạt động này trở thành những khối xây dựng của những hoạt động phức tạp hơn trên bất kỳ tập dữ liệu nào.Với ngôn ngữ đại số cơ bản này được triển khai một cách hiệu quả trong cơ sở dữ liệu hoặc chương trình khác, một loạt các hoạt động tổ hợp phức tạp khá có thể được thực hiện.

Pandas cài đặt một số khối xây dựng cơ bản này trong hàm pd.merge() và phương thức liên quan join() của SeriesDataframes.Như chúng ta sẽ thấy, chúng cho phép bạn kết nối dữ liệu từ các nguồn khác nhau một cách hiệu quả.

Các loại Join

Hàm pd.merge() thực hiện một số loại kết hợp: kết hợp một-một, kết hợp nhiều-một và kết hợp nhiều-nhiều.Ba loại kết hợp này được truy cập thông qua một cuộc gọi giống nhau với giao diện pd.merge(); loại kết hợp được thực hiện phụ thuộc vào hình thức dữ liệu đầu vào.Ở đây chúng tôi sẽ chỉ các ví dụ đơn giản về ba loại kết hợp này và thảo luận chi tiết về các tùy chọn dưới đây.

Kết hợp một-một

Có thể nói rằng, loại biểu thức gộp đơn giản nhất là phép gộp một-một, trong nhiều trường hợp rất tương tự việc nối cột mà chúng ta đã thấy trong Kết hợp datasets: Nối và Gộp.Ví dụ cụ thể, hãy xem xét hai DataFrames sau chứa thông tin về một số nhân viên trong một công ty:

df1 = pd.DataFrame({'employee': ['Bob', 'Jake', 'Lisa', 'Sue'],                    'group': ['Accounting', 'Engineering', 'Engineering', 'HR']})df2 = pd.DataFrame({'employee': ['Lisa', 'Bob', 'Jake', 'Sue'],                    'hire_date': [2004, 2008, 2012, 2014]})display('df1', 'df2')

df1

df2

Để kết hợp thông tin này vào một DataFrame duy nhất, chúng ta có thể sử dụng hàm pd.merge():

df3 = pd.merge(df1, df2)df3

Hàm pd.merge() nhận ra rằng mỗi DataFrame có một cột “employee” và tự động kết hợp bằng cách sử dụng cột này làm khóa.Kết quả của quá trình kết hợp là một DataFrame mới kết hợp thông tin từ hai nguồn dữ liệu đầu vào.Chú ý rằng thứ tự các mục trong mỗi cột không nhất thiết được duy trì: trong trường hợp này, thứ tự cột “employee” khác nhau giữa df1df2, và hàm pd.merge() đúng cho điều này.Thêm vào đó, hãy lưu ý rằng quá trình kết hợp thông thường loại bỏ chỉ mục, trừ trường hợp đặc biệt là kết hợp theo chỉ mục (xem từ khóa left_indexright_index, được thảo luận qua một lát).

Many-to-one joins

Các kết hợp one-to-many là kết hợp trong đó một trong hai cột khóa chứa các mục nhập trùng lặp.Trong trường hợp many-to-one, DataFrame kết quả sẽ bảo tồn các mục nhập trùng lặp đó theo cách thích hợp.Hãy xem ví dụ sau về một kết hợp many-to-one:

df4 = pd.DataFrame({'group': ['Accounting', 'Engineering', 'HR'],                    'supervisor': ['Carly', 'Guido', 'Steve']})display('df3', 'df4', 'pd.merge(df3, df4)')

df3

df4

pd.merge(df3, df4)

Kết quả của DataFrame có một cột bổ sung chứa thông tin về “giám đốc”, trong đó thông tin được lặp lại ở một hoặc nhiều vị trí tùy thuộc vào các đầu vào.

Các liên kết nhiều – nhiều

Các kết hợp nhiều-nhiều (many-to-many joins) là khá khó hiểu về mặt khái niệm, nhưng lại được xác định rõ ràng.Nếu cột khóa (key column) trong cả mảng trái và mảng phải đều chứa các bản sao, thì kết quả là một kết hợp nhiều-nhiều. Điều này sẽ rõ nhất thông qua một ví dụ cụ thể. Hãy xem ví dụ sau, nơi chúng ta có một DataFrame hiển thị một hoặc nhiều kỹ năng liên quan đến một nhóm cụ thể. Bằng cách thực hiện một kết hợp nhiều-nhiều, chúng ta có thể khôi phục lại các kỹ năng liên quan đến bất kỳ người nào:

df5 = pd.DataFrame({'group': ['Accounting', 'Accounting',                              'Engineering', 'Engineering', 'HR', 'HR'],                    'skills': ['math', 'spreadsheets', 'coding', 'linux',                               'spreadsheets', 'organization']})display('df1', 'df5', "pd.merge(df1, df5)")

df1

df5

pd.merge(df1, df5)

Ba loại join này có thể được sử dụng với các công cụ Pandas khác để thực hiện một loạt các chức năng.Nhưng trong thực tế, bộ dữ liệu hiếm khi được sạch như bộ dữ liệu mà chúng ta đang làm việc ở đây.Ở phần tiếp theo, chúng ta sẽ xem xét một số tùy chọn được cung cấp bởi pd.merge() để bạn có thể điều chỉnh cách hoạt động của các phép join.

Đặc tả của khóa Merge

Chúng ta đã thấy được hành vi mặc định của pd.merge(): nó tìm kiếm một hoặc nhiều tên cột khớp giữa hai đầu vào và sử dụng điều này như làm khóa.Tuy nhiên, thường tên cột sẽ không khớp một cách hoàn hảo và pd.merge() cung cấp một loạt các tùy chọn để xử lý điều này.

The on keyword

Một cách đơn giản nhất, bạn có thể chỉ định tường minh tên cột khóa bằng cách sử dụng từ khóa on, nó nhận tên cột hoặc danh sách tên cột:

display('df1', 'df2', "pd.merge(df1, df2, on='employee')")

df1

df2

pd.merge(df1, df2, on=’employee’)

Tùy chọn này chỉ hoạt động khi cả DataFrame trái và phải đều có tên cột được chỉ định.

Các từ khóa left_onright_on

Đôi khi bạn có thể muốn hợp nhất hai bộ dữ liệu có tên cột khác nhau; ví dụ, chúng ta có thể có một bộ dữ liệu trong đó tên nhân viên được đánh dấu là “name” thay vì “employee”.Trong trường hợp này, chúng ta có thể sử dụng từ khóa left_onright_on để chỉ định hai tên cột:

df3 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],                    'salary': [70000, 80000, 120000, 90000]})display('df1', 'df3', 'pd.merge(df1, df3, left_on="employee", right_on="name")')

df1

df3

pd.merge(df1, df3, left_on=”employee”, right_on=”name”)

Kết quả có một cột dư thừa có thể loại bỏ nếu muốn – ví dụ, bằng cách sử dụng phương thức drop() của các đối tượng DataFrame:

pd.merge(df1, df3, left_on="employee", right_on="name").drop('name', axis=1)

Từ khóa left_indexright_index

Đôi khi, thay vì gộp theo một cột, bạn có thể muốn gộp theo một chỉ mục.Ví dụ, dữ liệu của bạn có thể nhìn như sau:

df1a = df1.set_index('employee')df2a = df2.set_index('employee')display('df1a', 'df2a')

df1a

df2a

Bạn có thể sử dụng chỉ mục làm khóa để kết hợp bằng cách chỉ định các cờ left_index và / hoặc right_index trong pd.merge().

display('df1a', 'df2a',        "pd.merge(df1a, df2a, left_index=True, right_index=True)")

df1a

df2a

pd.merge(df1a, df2a, left_index=True, right_index=True)

Để tiện lợi, DataFrame cung cấp phương thức join(), thực hiện việc kết hợp mặc định dựa trên các chỉ số:

display('df1a', 'df2a', 'df1a.join(df2a)')

df1a

df2a

df1a.join(df2a)

Nếu bạn muốn kết hợp các chỉ mục và cột, bạn có thể kết hợp left_index với right_on hoặc left_on với right_index để đạt được hành vi mong muốn:

display('df1a', 'df3', "pd.merge(df1a, df3, left_index=True, right_on='name')")

df1a

df3

pd.merge(df1a, df3, left_index=True, right_on=’name’)

Tất cả các tùy chọn này cũng hoạt động với nhiều chỉ số và/hoặc nhiều cột; giao diện cho hành vi này rất trực quan.Để biết thêm thông tin về điều này, xem phần “Ghép, Liên kết và Nối” trong tài liệu Pandas.

Xác định Phép tính tập hợp cho Liên kết

Trong tất cả các ví dụ trước đó, chúng ta đã bỏ qua một yếu tố quan trọng khi thực hiện một phép kết nối: loại toán học tập hợp được sử dụng trong phép kết nối.Điều này xảy ra khi một giá trị xuất hiện trong một cột khóa nhưng không xuất hiện trong cột khóa khác. Xem ví dụ sau:

df6 = pd.DataFrame({'name': ['Peter', 'Paul', 'Mary'],                    'food': ['fish', 'beans', 'bread']},                   columns=['name', 'food'])df7 = pd.DataFrame({'name': ['Mary', 'Joseph'],                    'drink': ['wine', 'beer']},                   columns=['name', 'drink'])display('df6', 'df7', 'pd.merge(df6, df7)')

df6

df7

pd.merge(df6, df7)

Ở đây chúng ta đã trộn hai bộ dữ liệu chỉ có một mục “name” chung: Mary.Theo mặc định, kết quả chứa giao của hai tập hợp đầu vào; đây được gọi là inner join.Chúng ta có thể chỉ định điều này một cách rõ ràng bằng cách sử dụng từ khóa how, mặc định là "inner":

pd.merge(df6, df7, how='inner')

Các tùy chọn khác cho từ khóa how'outer', 'left''right'.Một gabung bên ngoài trả về một kết quả gabung trên tổng hợp các cột đầu vào và điền vào tất cả các giá trị thiếu bằng giá trị NA:

display('df6', 'df7', "pd.merge(df6, df7, how='outer')")

df6

None

pd.merge(df6, df7, how=’outer’)

Phép left joinright join trả về kết quả ghi nhận các liên kết từ các mục nhập bên trái và các mục nhập bên phải, tương ứng.Ví dụ:

display('df6', 'df7', "pd.merge(df6, df7, how='left')")

df6

df7

pd.merge(df6, df7, how=’left’)

Các hàng đầu ra hiện tại tương ứng với các nhập vào ở bên trái. Sử dụng how='right' hoạt động theo cách tương tự.

Tất cả các tùy chọn này có thể được áp dụng một cách trực tiếp cho bất kỳ kiểu join nào đã được đề cập trước đó.

Các Tên Cột Trùng Lắp: Từ khóa suffixes

Cuối cùng, bạn có thể gặp trường hợp hai đối tượng nhập vào DataFrame của bạn có tên cột xung đột.Hãy xem ví dụ sau:

df8 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],                    'rank': [1, 2, 3, 4]})df9 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],                    'rank': [3, 1, 4, 2]})display('df8', 'df9', 'pd.merge(df8, df9, on="name")')

df8

df9

pd.merge(df8, df9, on=”name”)

Vì đầu ra sẽ có hai tên cột xung đột, hàm merge tự động thêm một hậu tố _x hoặc _y để đảm bảo các cột đầu ra là duy nhất.

display('df8', 'df9', 'pd.merge(df8, df9, on="name", suffixes=["_L", "_R"])')

df8

df9

pd.merge(df8, df9, on=”name”, suffixes=[“_L”, “_R”])

Những hậu tố này hoạt động trong bất kỳ mẫu kết nối nào có thể, và cũng hoạt động nếu có nhiều cột chồng chéo.

Để biết thêm thông tin về các mẫu này, hãy xem Các mẫu tổng hợp và nhóm nơi chúng ta đi sâu vào algebra quan hệ. Xem thêm tài liệu Pandas “Merge, Join and Concatenate” để thảo luận sâu hơn về các chủ đề này.

Ví dụ: Dữ liệu các tiểu bang Mỹ

Các hoạt động Merge và join xuất hiện thường xuyên khi kết hợp dữ liệu từ các nguồn khác nhau.Ở đây chúng ta sẽ xem xét một ví dụ về một số dữ liệu về các tiểu bang và dân số của Mỹ.Các tệp dữ liệu có thể được tìm thấy tại http://github.com/jakevdp/data-USstates/:

# Following are shell commands to download the data# !curl -O https://raw.githubusercontent.com/jakevdp/data-USstates/master/state-population.csv# !curl -O https://raw.githubusercontent.com/jakevdp/data-USstates/master/state-areas.csv# !curl -O https://raw.githubusercontent.com/jakevdp/data-USstates/master/state-abbrevs.csv

Hãy cùng xem ba tập dữ liệu này, sử dụng hàm read_csv() của Pandas:

pop = pd.read_csv('data/state-population.csv')areas = pd.read_csv('data/state-areas.csv')abbrevs = pd.read_csv('data/state-abbrevs.csv')display('pop.head()', 'areas.head()', 'abbrevs.head()')

pop.head()

areas.đầu()

abbrevs.head()

Với thông tin này, giả sử chúng ta muốn tính toán một kết quả tương đối đơn giản: xếp hạng các bang và lãnh thổ của Mỹ theo mật độ dân số năm 2010.Chúng ta rõ ràng có dữ liệu ở đây để tìm kết quả này, nhưng chúng ta sẽ phải kết hợp các bộ dữ liệu để tìm kết quả.

Chúng ta sẽ bắt đầu với một phép gộp nhiều vào một sẽ cho chúng ta tên đầy đủ của tiểu bang trong DataFrame dân số.Chúng ta muốn gộp dựa trên cột state/region của pop, và cột abbreviation của abbrevs.Chúng ta sẽ sử dụng how='outer' để đảm bảo không có dữ liệu bị mất do nhãn không phù hợp.

merged = pd.merge(pop, abbrevs, how='outer',                  left_on='state/region', right_on='abbreviation')merged = merged.drop('abbreviation', 1) # drop duplicate infomerged.head()

Hãy kiểm tra xem có bất kỳ sự khác nhau nào ở đây không, điều chúng ta có thể làm bằng cách tìm kiếm các hàng có giá trị null:

merged.isnull().any()
state/region    Falseages            Falseyear            Falsepopulation       Truestate            Truedtype: bool

Một số thông tin về dân số là null; hãy tìm ra đó là những cái nào!

merged[merged['population'].isnull()].head()

Có vẻ như tất cả các giá trị dân số null đến từ Puerto Rico trước năm 2000; điều này có thể do dữ liệu này không khả dụng từ nguồn ban đầu.

Quan trọng hơn, chúng ta cũng thấy rằng một số state mới cũng là null, điều này có nghĩa là không có mục nhập tương ứng trong khóa abbrevs!Hãy xác định xem những vùng này thiếu khớp:

merged.loc[merged['state'].isnull(), 'state/region'].unique()
array(['PR', 'USA'], dtype=object)

Chúng ta có thể nhanh chóng suy ra vấn đề: dữ liệu dân số của chúng ta bao gồm các mục nhập cho Puerto Rico (PR) và Hoa Kỳ như một tổng thể (USA), trong khi các mục nhập này không xuất hiện trong khóa viết tắt của các tiểu bang.Chúng ta có thể nhanh chóng khắc phục điều này bằng cách điền các mục nhập phù hợp:

merged.loc[merged['state/region'] == 'PR', 'state'] = 'Puerto Rico'merged.loc[merged['state/region'] == 'USA', 'state'] = 'United States'merged.isnull().any()
state/region    Falseages            Falseyear            Falsepopulation       Truestate           Falsedtype: bool

Không còn giá trị rỗng trong cột state: chúng ta đã hoàn tất!

Bây giờ chúng ta có thể hợp nhất kết quả với dữ liệu khu vực bằng một thủ tục tương tự.Kiểm tra kết quả của chúng ta, chúng ta sẽ muốn kết hợp trên cột state trong cả hai:

final = pd.merge(merged, areas, on='state', how='left')final.head()

Một lần nữa, hãy kiểm tra giá trị null để xem có không đồng bộ nào hay không:

final.isnull().any()
state/region     Falseages             Falseyear             Falsepopulation        Truestate            Falsearea (sq. mi)     Truedtype: bool

Có giá trị null trong cột area; chúng ta có thể xem xem khu vực nào đã bị bỏ qua ở đây:

final['state'][final['area (sq. mi)'].isnull()].unique()
array(['United States'], dtype=object)

Chúng ta nhận thấy rằng DataFrame của chúng ta không chứa diện tích của Hoa Kỳ như một toàn thể.Chúng ta có thể chèn giá trị thích hợp (sử dụng tổng diện tích của tất cả các tiểu bang, ví dụ như), nhưng trong trường hợp này chúng ta chỉ đơn giản xóa các giá trị null vì mật độ dân số của toàn bộ Hoa Kỳ không liên quan đến cuộc thảo luận hiện tại của chúng ta:

final.dropna(inplace=True)final.head()

Bây giờ chúng ta đã có tất cả dữ liệu cần thiết. Để trả lời cho câu hỏi quan tâm, hãy trước tiên chọn phần của dữ liệu liên quan với năm 2000 và tổng dân số.Chúng ta sẽ sử dụng hàm query() để làm điều này nhanh chóng (điều này yêu cầu phải cài đặt gói numexpr; xem Pandas Hiệu suất cao: eval()query()):

data2010 = final.query("year == 2010 & ages == 'total'")data2010.head()

Bây giờ chúng ta sẽ tính mật độ dân số và hiển thị nó theo thứ tự.Chúng ta sẽ bắt đầu bằng cách chỉ mục lại dữ liệu của chúng ta theo từng bang, sau đó tính kết quả:

data2010.set_index('state', inplace=True)density = data2010['population'] / data2010['area (sq. mi)']
density.sort_values(ascending=False, inplace=True)density.head()
stateDistrict of Columbia    8898.897059Puerto Rico             1058.665149New Jersey              1009.253268Rhode Island             681.339159Connecticut              645.600649dtype: float64

Kết quả là một bảng xếp hạng các bang của Hoa Kỳ cùng với Washington, DC và Puerto Rico theo thứ tự mật độ dân số năm 2010, tính bằng số dân trên mỗi mile vuông.Chúng ta có thể thấy rằng khu vực có mật độ dân số cao nhất trong bộ dữ liệu này là Washington, DC (tức là Quận cộng hòa); trong số các bang, bang có mật độ dân số cao nhất là New Jersey.

Chúng ta cũng có thể kiểm tra cuối của danh sách:

density.tail()
stateSouth Dakota    10.583512North Dakota     9.537565Montana          6.736171Wyoming          5.768079Alaska           1.087509dtype: float64

Chúng ta nhận thấy rằng tiểu bang ít dân nhất, hoàn toàn là Alaska, trung bình có hơn một người cư trú trên mỗi dặm vuông.

Loại công việc hợp nhất dữ liệu lộn xộn như này là một nhiệm vụ phổ biến khi cố gắng trả lời các câu hỏi bằng cách sử dụng các nguồn dữ liệu thực tế.Tôi hy vọng rằng ví dụ này đã cho bạn một ý tưởng về cách bạn có thể kết hợp các công cụ chúng ta đã trình bày để có cái nhìn sâu sắc từ dữ liệu của bạn!