7.10 組合數(shù)據(jù)集:合并和連接
原文:Combining Datasets: Merge and Join
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
本節(jié)是《Python 數(shù)據(jù)科學(xué)手冊(cè)》(Python Data Science Handbook)的摘錄。
Pandas 提供的一個(gè)基本特性,是內(nèi)存中的高性能的連接和合并操作。如果你曾經(jīng)使用過(guò)數(shù)據(jù)庫(kù),那么你應(yīng)該熟悉這種類型的數(shù)據(jù)交互。它的主要接口是pd.merge函數(shù),我們將看到幾個(gè)在實(shí)踐中如何工作的例子。
為方便起見(jiàn),我們將從重新定義上一節(jié)的display()函數(shù)開(kāi)始:
import pandas as pd
import numpy as np
class display(object):
"""Display HTML representation of multiple objects"""
template = """<div style="float: left; padding: 10px;">
<p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1}
</div>"""
def __init__(self, *args):
self.args = args
def _repr_html_(self):
return '\n'.join(self.template.format(a, eval(a)._repr_html_())
for a in self.args)
def __repr__(self):
return '\n\n'.join(a + '\n' + repr(eval(a))
for a in self.args)
關(guān)系代數(shù)
pd.merge()中實(shí)現(xiàn)的行為,是所謂的關(guān)系代數(shù)的一個(gè)子集,它是一組用于操縱關(guān)系數(shù)據(jù)的形式規(guī)則,并形成了大多數(shù)數(shù)據(jù)庫(kù)中可用操作的概念基礎(chǔ)。關(guān)系代數(shù)方法的優(yōu)勢(shì)在于它提出了幾種原始操作,這些操作成為任何數(shù)據(jù)集上更復(fù)雜操作的積木。擁有在數(shù)據(jù)庫(kù)或其他程序中高效實(shí)現(xiàn)的基本操作詞典,可以執(zhí)行各種相當(dāng)復(fù)雜的復(fù)合操作。
Pandas 在pd.merge()函數(shù)和Series和Dataframe的相關(guān)join()方法中,實(shí)現(xiàn)了幾個(gè)基本構(gòu)建塊。正如我們將看到的,這些可以讓你有效地鏈接來(lái)自不同來(lái)源的數(shù)據(jù)。
連接的分類
pd.merge()函數(shù)實(shí)現(xiàn)了許多類型的連接:一對(duì)一,多對(duì)一和多對(duì)多連接。通過(guò)以相同方式調(diào)用pd.merge()接口,來(lái)訪問(wèn)所有三種類型的連接;執(zhí)行的連接類型取決于輸入數(shù)據(jù)的形式。這里我們將展示三種合并的簡(jiǎn)單示例,并在下面進(jìn)一步討論詳細(xì)選項(xiàng)。
一對(duì)一連接
也許最簡(jiǎn)單的合并表達(dá)式是一對(duì)一連接,這在很多方面與“數(shù)據(jù)集的組合:連接和附加”中的按列連接非常相似。。作為一個(gè)具體的例子,考慮以下兩個(gè)DataFrame,它們包含公司中幾個(gè)員工的信息:
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:
| employee | group | |
|---|---|---|
| 0 | Bob | Accounting |
| 1 | Jake | Engineering |
| 2 | Lisa | Engineering |
| 3 | Sue | HR |
df2:
| employee | hire_date | |
|---|---|---|
| 0 | Lisa | 2004 |
| 1 | Bob | 2008 |
| 2 | Jake | 2012 |
| 3 | Sue | 2014 |
要將這些信息組合成一個(gè)DataFrame,我們可以使用pd.merge()函數(shù):
df3 = pd.merge(df1, df2)
df3
| employee | group | hire_date | |
|---|---|---|---|
| 0 | Bob | Accounting | 2008 |
| 1 | Jake | Engineering | 2012 |
| 2 | Lisa | Engineering | 2004 |
| 3 | Sue | HR | 2014 |
pd.merge()函數(shù)識(shí)別,每個(gè)DataFrame都有一個(gè)employee列,并使用該列作為鍵自動(dòng)連接。合并的結(jié)果是一個(gè)新的DataFrame,它組合了兩個(gè)輸入的信息。
請(qǐng)注意,每列中的條目順序不一定得到保留:在這種情況下,employee列的順序在df1和df2之間有所不同。pd.merge()函數(shù)正確地解釋了這一點(diǎn)。另外,請(qǐng)記住,合并一般會(huì)丟棄索引,除了在索引合并的特殊情況下(參見(jiàn)left_index和right_index關(guān)鍵字,之后討論)。
多對(duì)一連接
多對(duì)一連接中,兩個(gè)鍵列中的一個(gè)包含重復(fù)條目。對(duì)于多對(duì)一的情況,生成的DataFrame將保留適當(dāng)?shù)闹貜?fù)條目??紤]以下多對(duì)一連接的示例:
df4 = pd.DataFrame({'group': ['Accounting', 'Engineering', 'HR'],
'supervisor': ['Carly', 'Guido', 'Steve']})
display('df3', 'df4', 'pd.merge(df3, df4)')
df3:
| employee | group | hire_date | |
|---|---|---|---|
| 0 | Bob | Accounting | 2008 |
| 1 | Jake | Engineering | 2012 |
| 2 | Lisa | Engineering | 2004 |
| 3 | Sue | HR | 2014 |
df4:
| group | supervisor | |
|---|---|---|
| 0 | Accounting | Carly |
| 1 | Engineering | Guido |
| 2 | HR | Steve |
pd.merge(df3, df4):
| employee | group | hire_date | supervisor | |
|---|---|---|---|---|
| 0 | Bob | Accounting | 2008 | Carly |
| 1 | Jake | Engineering | 2012 | Guido |
| 2 | Lisa | Engineering | 2004 | Guido |
| 3 | Sue | HR | 2014 | Steve |
生成的DataFrame擁有帶有supervisor信息的附加列,其中信息在輸入所需的一個(gè)或多個(gè)位置重復(fù)。
多對(duì)多連接
多對(duì)多連接在概念上有點(diǎn)令人困惑,但仍然有很好的定義。如果左側(cè)和右側(cè)數(shù)組中的鍵列都包含重復(fù)項(xiàng),則結(jié)果是多對(duì)多合并。
結(jié)合一個(gè)具體的例子可能是最清楚的。考慮以下內(nèi)容,我們有一個(gè)DataFrame,展示了與特定分組相關(guān)的一項(xiàng)或多項(xiàng)技能。通過(guò)執(zhí)行多對(duì)多連接,我們可以恢復(fù)與任何個(gè)人相關(guān)的技能:
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:
| employee | group | |
|---|---|---|
| 0 | Bob | Accounting |
| 1 | Jake | Engineering |
| 2 | Lisa | Engineering |
| 3 | Sue | HR |
df5:
| group | skills | |
|---|---|---|
| 0 | Accounting | math |
| 1 | Accounting | spreadsheets |
| 2 | Engineering | coding |
| 3 | Engineering | linux |
| 4 | HR | spreadsheets |
| 5 | HR | organization |
pd.merge(df1, df5):
| employee | group | skills | |
|---|---|---|---|
| 0 | Bob | Accounting | math |
| 1 | Bob | Accounting | spreadsheets |
| 2 | Jake | Engineering | coding |
| 3 | Jake | Engineering | linux |
| 4 | Lisa | Engineering | coding |
| 5 | Lisa | Engineering | linux |
| 6 | Sue | HR | spreadsheets |
| 7 | Sue | HR | organization |
這三種類型的連接可以與其他 Pandas 工具一起使用,以實(shí)現(xiàn)各種功能。但實(shí)際上,數(shù)據(jù)集很少像我們?cè)谶@里使用的那樣干凈。在下一節(jié)中,我們將考慮pd.merge()提供的一些選項(xiàng),使你能夠調(diào)整連接操作的工作方式。
指定合并鍵
我們已經(jīng)看到了pd.merge()的默認(rèn)行為:它在兩個(gè)輸入之間查找一個(gè)或多個(gè)匹配的列名,并將其用作鍵。但是,通常列名稱不能很好地匹配,而pd.merge()提供了各種處理它的選項(xiàng)。
on關(guān)鍵字
Most simply, you can explicitly specify the name of the key column using the on keyword, which takes a column name or a list of column names:
display('df1', 'df2', "pd.merge(df1, df2, on='employee')")
df1:
| employee | group | |
|---|---|---|
| 0 | Bob | Accounting |
| 1 | Jake | Engineering |
| 2 | Lisa | Engineering |
| 3 | Sue | HR |
df2:
| employee | hire_date | |
|---|---|---|
| 0 | Lisa | 2004 |
| 1 | Bob | 2008 |
| 2 | Jake | 2012 |
| 3 | Sue | 2014 |
pd.merge(df1, df2, on='employee'):
| employee | group | hire_date | |
|---|---|---|---|
| 0 | Bob | Accounting | 2008 |
| 1 | Jake | Engineering | 2012 |
| 2 | Lisa | Engineering | 2004 |
| 3 | Sue | HR | 2014 |
僅當(dāng)左側(cè)和右側(cè)DataFrame都具有指定的列名時(shí),此選項(xiàng)才有效。
left_on和right_on關(guān)鍵字
有時(shí)你可能希望合并具有不同列名的兩個(gè)數(shù)據(jù)集;例如,我們可能有一個(gè)數(shù)據(jù)集,其中員工姓名被標(biāo)記為name而不是employee。在這種情況下,我們可以使用left_on和right_on關(guān)鍵字來(lái)指定兩個(gè)列名:
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:
| employee | group | |
|---|---|---|
| 0 | Bob | Accounting |
| 1 | Jake | Engineering |
| 2 | Lisa | Engineering |
| 3 | Sue | HR |
df3:
| name | salary | |
|---|---|---|
| 0 | Bob | 70000 |
| 1 | Jake | 80000 |
| 2 | Lisa | 120000 |
| 3 | Sue | 90000 |
pd.merge(df1, df3, left_on="employee", right_on="name"):
| employee | group | name | salary | |
|---|---|---|---|---|
| 0 | Bob | Accounting | Bob | 70000 |
| 1 | Jake | Engineering | Jake | 80000 |
| 2 | Lisa | Engineering | Lisa | 120000 |
| 3 | Sue | HR | Sue | 90000 |
結(jié)果有一個(gè)冗余列,如果需要我們可以刪除 - 例如,通過(guò)使用DataFrame的drop()方法:
pd.merge(df1, df3, left_on="employee", right_on="name").drop('name', axis=1)
| employee | group | salary | |
|---|---|---|---|
| 0 | Bob | Accounting | 70000 |
| 1 | Jake | Engineering | 80000 |
| 2 | Lisa | Engineering | 120000 |
| 3 | Sue | HR | 90000 |
left_index和right_index關(guān)鍵字
有時(shí),你寧愿按索引合并,而不是按列合并。例如,你的數(shù)據(jù)可能如下所示:
df1a = df1.set_index('employee')
df2a = df2.set_index('employee')
display('df1a', 'df2a')
df1a:
| group | |
|---|---|
| employee | |
| Bob | Accounting |
| Jake | Engineering |
| Lisa | Engineering |
| Sue | HR |
df2a:
| hire_date | |
|---|---|
| employee | |
| Lisa | 2004 |
| Bob | 2008 |
| Jake | 2012 |
| Sue | 2014 |
你可以通過(guò)在pd.merge()中指定left_index和/或right_index標(biāo)志,來(lái)將索引用作合并的鍵:
display('df1a', 'df2a',
"pd.merge(df1a, df2a, left_index=True, right_index=True)")
df1a:
| group | |
|---|---|
| employee | |
| Bob | Accounting |
| Jake | Engineering |
| Lisa | Engineering |
| Sue | HR |
df2a:
| hire_date | |
|---|---|
| employee | |
| Lisa | 2004 |
| Bob | 2008 |
| Jake | 2012 |
| Sue | 2014 |
pd.merge(df1a, df2a, left_index=True, right_index=True):
| group | hire_date | |
|---|---|---|
| employee | ||
| Lisa | Engineering | 2004 |
| Bob | Accounting | 2008 |
| Jake | Engineering | 2012 |
| Sue | HR | 2014 |
為方便起見(jiàn),DataFrame`實(shí)現(xiàn)join()``方法,該方法執(zhí)行的合并默認(rèn)為連接索引:
display('df1a', 'df2a', 'df1a.join(df2a)')
df1a:
| group | |
|---|---|
| employee | |
| Bob | Accounting |
| Jake | Engineering |
| Lisa | Engineering |
| Sue | HR |
df2a:
| hire_date | |
|---|---|
| employee | |
| Lisa | 2004 |
| Bob | 2008 |
| Jake | 2012 |
| Sue | 2014 |
df1a.join(df2a):
| group | hire_date | |
|---|---|---|
| employee | ||
| Bob | Accounting | 2008 |
| Jake | Engineering | 2012 |
| Lisa | Engineering | 2004 |
| Sue | HR | 2014 |
如果你想混合索引和列,你可以將left_index和right_on或left_on和right_index結(jié)合起來(lái),來(lái)獲得所需的行為:
display('df1a', 'df3', "pd.merge(df1a, df3, left_index=True, right_on='name')")
df1a:
| group | |
|---|---|
| employee | |
| Bob | Accounting |
| Jake | Engineering |
| Lisa | Engineering |
| Sue | HR |
df3:
| name | salary | |
|---|---|---|
| 0 | Bob | 70000 |
| 1 | Jake | 80000 |
| 2 | Lisa | 120000 |
| 3 | Sue | 90000 |
pd.merge(df1a, df3, left_index=True, right_on='name'):
| group | name | salary | |
|---|---|---|---|
| 0 | Accounting | Bob | 70000 |
| 1 | Engineering | Jake | 80000 |
| 2 | Engineering | Lisa | 120000 |
| 3 | HR | Sue | 90000 |
所有這些選項(xiàng)也適用于多重索引和/或多個(gè)列;這種行為的接口非常直觀。此內(nèi)容的更多信息,請(qǐng)參閱 Pandas 文檔的“合并,連接(Join)和連接(concat)”一節(jié)。
為連接指定集合運(yùn)算
在前面的所有例子中,我們?cè)趫?zhí)行連接時(shí)掩蓋了一個(gè)重要的考慮因素:連接中使用的集合運(yùn)算的類型。當(dāng)一個(gè)值出現(xiàn)在一個(gè)鍵列而不出現(xiàn)在另一個(gè)鍵列中時(shí),會(huì)出現(xiàn)此情況。 考慮這個(gè)例子:
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:
| name | food | |
|---|---|---|
| 0 | Peter | fish |
| 1 | Paul | beans |
| 2 | Mary | bread |
df7:
| name | drink | |
|---|---|---|
| 0 | Mary | wine |
| 1 | Joseph | beer |
pd.merge(df6, df7):
| name | food | drink | |
|---|---|---|---|
| 0 | Mary | bread | wine |
在這里,我們合并了兩個(gè)數(shù)據(jù)集,它們只有一個(gè)相同的name條目:Mary。默認(rèn)情況下,結(jié)果包含兩組輸入的交集;這就是所謂的內(nèi)連接。我們可以使用how關(guān)鍵字明確指定它,默認(rèn)為"inner":
pd.merge(df6, df7, how='inner')
| name | food | drink | |
|---|---|---|---|
| 0 | Mary | bread | wine |
how關(guān)鍵字的其他選項(xiàng)是'outer','left'和'right'。外連接返回輸入列的并集上的連接,并使用 NA 填充所有缺少的值:
display('df6', 'df7', "pd.merge(df6, df7, how='outer')")
df6:
| name | food | |
|---|---|---|
| 0 | Peter | fish |
| 1 | Paul | beans |
| 2 | Mary | bread |
df7:
| name | drink | |
|---|---|---|
| 0 | Mary | wine |
| 1 | Joseph | beer |
pd.merge(df6, df7, how='outer'):
| name | food | drink | |
|---|---|---|---|
| 0 | Peter | fish | NaN |
| 1 | Paul | beans | NaN |
| 2 | Mary | bread | wine |
| 3 | Joseph | NaN | beer |
左連接和右連接分別返回左側(cè)條目和右側(cè)條目上的連接。例如:
display('df6', 'df7', "pd.merge(df6, df7, how='left')")
df6:
| name | food | |
|---|---|---|
| 0 | Peter | fish |
| 1 | Paul | beans |
| 2 | Mary | bread |
df7:
| name | drink | |
|---|---|---|
| 0 | Mary | wine |
| 1 | Joseph | beer |
pd.merge(df6, df7, how='left'):
| | name | food | drink |
| --- | --- | --- |
| 0 | Peter | fish | NaN |
| 1 | Paul | beans | NaN |
| 2 | Mary | bread | wine |
輸出行現(xiàn)在對(duì)應(yīng)于左輸入中的條目。how ='right'以類似的方式工作。所有這些選項(xiàng)都可以直接應(yīng)用于任何前面的連接類型。
覆蓋列名:suffixes關(guān)鍵字
最后,你最終可能會(huì)遇到兩個(gè)輸入DataFrame具有沖突列名的情況??紤]這個(gè)例子:
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:
| name | rank | |
|---|---|---|
| 0 | Bob | 1 |
| 1 | Jake | 2 |
| 2 | Lisa | 3 |
| 3 | Sue | 4 |
df9:
| name | rank | |
|---|---|---|
| 0 | Bob | 3 |
| 1 | Jake | 1 |
| 2 | Lisa | 4 |
| 3 | Sue | 2 |
pd.merge(df8, df9, on="name"):
| name | rank_x | rank_y | |
|---|---|---|---|
| 0 | Bob | 1 | 3 |
| 1 | Jake | 2 | 1 |
| 2 | Lisa | 3 | 4 |
| 3 | Sue | 4 | 2 |
因?yàn)檩敵鲇袃蓚€(gè)沖突的列名,merge函數(shù)會(huì)自動(dòng)附加后綴_x或_y來(lái)使輸出列唯一。如果這些默認(rèn)值不合適,可以使用suffixes關(guān)鍵字指定自定義后綴:
display('df8', 'df9', 'pd.merge(df8, df9, on="name", suffixes=["_L", "_R"])')
df8:
| name | rank | |
|---|---|---|
| 0 | Bob | 1 |
| 1 | Jake | 2 |
| 2 | Lisa | 3 |
| 3 | Sue | 4 |
df9:
| name | rank | |
|---|---|---|
| 0 | Bob | 3 |
| 1 | Jake | 1 |
| 2 | Lisa | 4 |
| 3 | Sue | 2 |
pd.merge(df8, df9, on="name", suffixes=["_L", "_R"]):
| name | rank_L | rank_R | |
|---|---|---|---|
| 0 | Bob | 1 | 3 |
| 1 | Jake | 2 | 1 |
| 2 | Lisa | 3 | 4 |
| 3 | Sue | 4 | 2 |
這些后綴適用于任何可能的連接模式,并且如果存在多個(gè)重疊列,則也有效。這些模式的更多信息,請(qǐng)參閱“聚合和分組”,其中我們深入研究了關(guān)系代數(shù)。這些主題的進(jìn)一步討論,請(qǐng)參閱[Pandas“合并,連接(Join)和連接(Concatenate)文檔”。
示例:美國(guó)各州數(shù)據(jù)
在組合來(lái)自不同來(lái)源的數(shù)據(jù)時(shí),合并和連接操作最常出現(xiàn)。在這里,我們將考慮美國(guó)各州及其人口數(shù)據(jù)的一些例子。數(shù)據(jù)文件可以在 http://github.com/jakevdp/data-USstates/ 找到:
# 下面是下載數(shù)據(jù)的 shell 命令
# !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
讓我們看一下三個(gè)數(shù)據(jù)集,使用 Pandas read_csv()函數(shù):
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():
| state/region | ages | year | population | |
|---|---|---|---|---|
| 0 | AL | under18 | 2012 | 1117489.0 |
| 1 | AL | total | 2012 | 4817528.0 |
| 2 | AL | under18 | 2010 | 1130966.0 |
| 3 | AL | total | 2010 | 4785570.0 |
| 4 | AL | under18 | 2011 | 1125763.0 |
areas.head():
| state | area (sq. mi) | |
|---|---|---|
| 0 | Alabama | 52423 |
| 1 | Alaska | 656425 |
| 2 | Arizona | 114006 |
| 3 | Arkansas | 53182 |
| 4 | California | 163707 |
abbrevs.head():
| state | abbreviation | |
|---|---|---|
| 0 | Alabama | AL |
| 1 | Alaska | AK |
| 2 | Arizona | AZ |
| 3 | Arkansas | AR |
| 4 | California | CA |
根據(jù)這些信息,我們想要計(jì)算一個(gè)相對(duì)簡(jiǎn)單的結(jié)果:根據(jù) 2010 年人口密度對(duì)美國(guó)各州和地區(qū)進(jìn)行排名。顯然,我們?cè)谶@里擁有用于找到這個(gè)結(jié)果的數(shù)據(jù),但是我們必須結(jié)合數(shù)據(jù)集來(lái)找到結(jié)果。
我們將從多對(duì)一合并開(kāi)始,它將向我們提供人口DataFrame中的完整的州名。我們想要根據(jù)pop的state/region列和abbrevs的abbreviation列進(jìn)行合并。我們將使用how ='outer'來(lái)確保沒(méi)有數(shù)據(jù)因標(biāo)簽不匹配而被丟棄。
merged = pd.merge(pop, abbrevs, how='outer',
left_on='state/region', right_on='abbreviation')
merged = merged.drop('abbreviation', 1) # 丟棄重復(fù)的數(shù)據(jù)
merged.head()
| state/region | ages | year | population | state | |
|---|---|---|---|---|---|
| 0 | AL | under18 | 2012 | 1117489.0 | Alabama |
| 1 | AL | total | 2012 | 4817528.0 | Alabama |
| 2 | AL | under18 | 2010 | 1130966.0 | Alabama |
| 3 | AL | total | 2010 | 4785570.0 | Alabama |
| 4 | AL | under18 | 2011 | 1125763.0 | Alabama |
讓我們仔細(xì)檢查這里是否存在任何不匹配,我們可以通過(guò)查找?guī)в锌罩档男衼?lái)實(shí)現(xiàn):
merged.isnull().any()
'''
state/region False
ages False
year False
population True
state True
dtype: bool
'''
一些population信息為空;讓我們弄清楚這些是什么!
merged[merged['population'].isnull()].head()
| state/region | ages | year | population | state | |
|---|---|---|---|---|---|
| 2448 | PR | under18 | 1990 | NaN | NaN |
| 2449 | PR | total | 1990 | NaN | NaN |
| 2450 | PR | total | 1991 | NaN | NaN |
| 2451 | PR | under18 | 1991 | NaN | NaN |
| 2452 | PR | total | 1993 | NaN | NaN |
似乎所有空的人口值都來(lái)自 2000 年之前的波多黎各;這可能是由于數(shù)據(jù)從原始來(lái)源無(wú)法獲得。
更重要的是,我們還看到一些新的state條目也是控制,這意味著abbrevs鍵中沒(méi)有相應(yīng)的條目!讓我們弄清楚哪些地區(qū)缺少這種匹配:
merged.loc[merged['state'].isnull(), 'state/region'].unique()
# array(['PR', 'USA'], dtype=object)
我們可以快速推斷出這個(gè)問(wèn)題:我們的人口數(shù)據(jù)包括波多黎各(PR)和整個(gè)美國(guó)(美國(guó))的條目,而這些條目沒(méi)有出現(xiàn)在州縮寫(xiě)的鍵中。我們可以通過(guò)填充適當(dāng)?shù)臈l目來(lái)快速解決這些問(wèn)題。
merged.loc[merged['state/region'] == 'PR', 'state'] = 'Puerto Rico'
merged.loc[merged['state/region'] == 'USA', 'state'] = 'United States'
merged.isnull().any()
'''
state/region False
ages False
year False
population True
state False
dtype: bool
'''
state列中沒(méi)有更多的空值:我們?nèi)几愣耍?/p>
現(xiàn)在我們可以使用類似的過(guò)程,來(lái)合并結(jié)果和面積數(shù)據(jù)。檢查我們的結(jié)果,我們將想要連接二者中的state列:
final = pd.merge(merged, areas, on='state', how='left')
final.head()
| state/region | ages | year | population | state | area (sq. mi) | |
|---|---|---|---|---|---|---|
| 0 | AL | under18 | 2012 | 1117489.0 | Alabama | 52423.0 |
| 1 | AL | total | 2012 | 4817528.0 | Alabama | 52423.0 |
| 2 | AL | under18 | 2010 | 1130966.0 | Alabama | 52423.0 |
| 3 | AL | total | 2010 | 4785570.0 | Alabama | 52423.0 |
| 4 | AL | under18 | 2011 | 1125763.0 | Alabama | 52423.0 |
再次,讓我們檢查空值來(lái)查看是否存在任何不匹配:
final.isnull().any()
'''
state/region False
ages False
year False
population True
state False
area (sq. mi) True
dtype: bool
'''
area列中有空值; 我們可以看看這里忽略了哪些區(qū)域:
final['state'][final['area (sq. mi)'].isnull()].unique()
# array(['United States'], dtype=object)
我們看到我們的areas DataFrame不包含整個(gè)美國(guó)的面積。我們可以插入適當(dāng)?shù)闹担ɡ?,使用所有州的面積總和),但在這種情況下,我們只會(huì)刪除空值,因?yàn)檎麄€(gè)美國(guó)的人口密度與我們當(dāng)前的討論無(wú)關(guān):
final.dropna(inplace=True)
final.head()
| state/region | ages | year | population | state | area (sq. mi) | |
|---|---|---|---|---|---|---|
| 0 | AL | under18 | 2012 | 1117489.0 | Alabama | 52423.0 |
| 1 | AL | total | 2012 | 4817528.0 | Alabama | 52423.0 |
| 2 | AL | under18 | 2010 | 1130966.0 | Alabama | 52423.0 |
| 3 | AL | total | 2010 | 4785570.0 | Alabama | 52423.0 |
| 4 | AL | under18 | 2011 | 1125763.0 | Alabama | 52423.0 |
現(xiàn)在我們擁有了所需的所有數(shù)據(jù)。 為了回答感興趣的問(wèn)題,讓我們首先選擇對(duì)應(yīng) 2000 年的數(shù)據(jù)部分和總?cè)丝?。我們將使?code>query()函數(shù)快速執(zhí)行此操作(這需要安裝numexpr包;參見(jiàn)“高性能 Pandas:eval()和query()”):
data2010 = final.query("year == 2010 & ages == 'total'")
data2010.head()
| state/region | ages | year | population | state | area (sq. mi) | |
|---|---|---|---|---|---|---|
| 3 | AL | total | 2010 | 4785570.0 | Alabama | 52423.0 |
| 91 | AK | total | 2010 | 713868.0 | Alaska | 656425.0 |
| 101 | AZ | total | 2010 | 6408790.0 | Arizona | 114006.0 |
| 189 | AR | total | 2010 | 2922280.0 | Arkansas | 53182.0 |
| 197 | CA | total | 2010 | 37333601.0 | California | 163707.0 |
現(xiàn)在讓我們計(jì)算人口密度并按順序顯示。我們首先重索引各州數(shù)據(jù),然后計(jì)算結(jié)果:
data2010.set_index('state', inplace=True)
density = data2010['population'] / data2010['area (sq. mi)']
density.sort_values(ascending=False, inplace=True)
density.head()
'''
state
District of Columbia 8898.897059
Puerto Rico 1058.665149
New Jersey 1009.253268
Rhode Island 681.339159
Connecticut 645.600649
dtype: float64
'''
結(jié)果是美國(guó)各州,華盛頓特區(qū)和波多黎各按其 2010 年人口密度的排名,以每平方英里居民為單位。我們可以看到,到目前為止,該數(shù)據(jù)集中最密集的區(qū)域是華盛頓特區(qū)(即哥倫比亞特區(qū));在各州之間,最密集的是新澤西州。
我們還可以查看列表的末尾:
density.tail()
'''
state
South Dakota 10.583512
North Dakota 9.537565
Montana 6.736171
Wyoming 5.768079
Alaska 1.087509
dtype: float64
'''
到目前為止,我們看到最不密集的州是阿拉斯加州,每平方英里平均略多于一個(gè)居民。
嘗試使用真實(shí)數(shù)據(jù)源回答問(wèn)題時(shí),這種混亂的數(shù)據(jù)合并是一項(xiàng)常見(jiàn)任務(wù)。我希望這個(gè)例子讓你了解,如何組合我們所涵蓋的工具,來(lái)從你的數(shù)據(jù)中獲得見(jiàn)解!