1. 程式人生 > >資料分析---《Python for Data Analysis》學習筆記【03】

資料分析---《Python for Data Analysis》學習筆記【03】

《Python for Data Analysis》一書由Wes Mckinney所著,中文譯名是《利用Python進行資料分析》。這裡記錄一下學習過程,其中有些方法和書中不同,是按自己比較熟悉的方式實現的。

 

第三個例項:US Baby Names 1880-2010

 

簡介: 美國社會保障總署(SSA)提供了一份從1880年到2010年的嬰兒姓名頻率的資料。

 

資料地址: https://github.com/wesm/pydata-book/tree/2nd-edition/datasets/babynames

 

準備工作:匯入pandas和matplotlib

import pandas as pd
import matplotlib.pyplot as plt
fig,ax=plt.subplots()

 

我們現在擁有的資料檔案是從1880年-2010年的嬰兒姓名頻率的.txt檔案,檔案如下所示:

Mary,F,7065
Anna,F,2604
Emma,F,2003
Elizabeth,F,1939
Minnie,F,1746

其中第一列是姓名,第二列是性別,第三列是當年此性別嬰兒中取該名字的人數。

 

檔案內容以逗號分隔,因此可以用pandas的read_csv方法讀取:

file=pd.read_csv(r"
...\yob1880.txt", header=None, names=["Name", "Gender", "Birth"])

現在讀取的是1880年的檔案,檔案沒有列名,因此header設為None,同時自己設定列名分別為Name, Gender和Birth。

 

我們需要給檔案加上年份這一列,以便知道某姓名頻次是屬於哪一年的:

file["Year"]=1880

 

現在讀取的1880年的檔案顯示如下:

        Name Gender  Birth  Year
0       Mary      F   7065  1880
1       Anna      F   2604  1880
2       Emma      F   2003  1880
3  Elizabeth      F   1939  1880
4     Minnie      F   1746  1880

 

但是我們有很多份這樣的檔案,不可能一個一個地手動讀取。因此,我們利用迴圈語句來讀取每份檔案,並把這些檔案用concat命令合併在一起。

file_list=[]  #設定空列表,作為合併列表

for year in range(1880,2011):
    file=pd.read_csv(r"...\yob{}.txt".format(year), header=None, names=["Name", "Gender", "Birth"])
    file["Year"]=year
    file_list.append(file)
    
data=pd.concat(file_list, ignore_index=True)

concat命令可以把pandas物件按行或列合併起來,命令接收的是一個序列。因此,我們首先設定一個空列表,然後迴圈讀取檔案,把檔案變為pandas物件,並放入該列表中。這裡注意,我們不需要保留原來的索引,因此設定ignore_index引數為True。

 

接下來,我們就可以開始分析了。

 

讓我們先來看看每一年的男女出生總人數分別是多少。

total_birth=pd.pivot_table(data, values="Birth", index="Year", columns="Gender", aggfunc="sum")
Gender       F       M
Year                  
1880     90993  110493
1881     91955  100748
1882    107851  113687
1883    112322  104632
1884    129021  114445
1885    133056  107802
1886    144538  110785
1887    145983  101412
1888    178631  120857
1889    178369  110590

 

用折線圖畫出來:

ax.plot(range(1880,2011), total_birth["F"], label="Female")
ax.plot(range(1880,2011), total_birth["M"], label="Male")
ax.legend()
ax.set_xticks(range(1880,2021,20))
ax.set_xlim(1880,2020)

plt.show()

可以看出,每一年男女出生人數都差不多。

 

接下來,我們想知道某個名字在某年某性別出生嬰兒中佔比多少。比如:Mary在1880年女性姓名中佔比多少,假設結果是0.02,那就說明每100個1880年出生的女嬰,有2個被命名為Mary。

 

我們把女性Mary在1880年對應的出生人數提取出來,然後再把1880年所有女性出生人數提取出來,把兩者相除,就得到了(Mary,女,1880年)的百分比。

 

如果要把所有的姓名百分比計算出來,那麼我們可以先把資料按年份和性別分類,然後再用apply方法對所有分類資料進行計算。

 

把資料按年份和性別分類:

by_year_gender=data.groupby(["Year", "Gender"])

 

定義apply方法所需的函式(由於該函式比較簡單,因此這裡直接用lambda函式):

lambda x: x["Birth"]/x["Birth"].sum()

 

把函式用於分類資料:

percentage=by_year_gender.apply(lambda x: x["Birth"]/x["Birth"].sum())

 

percentage的前10行顯示如下:

Year  Gender   
1880  F       0    0.077643
              1    0.028618
              2    0.022013
              3    0.021309
              4    0.019188
              5    0.017342
              6    0.016177
              7    0.015540
              8    0.014507
              9    0.014155
Name: Birth, dtype: float64

 

由於percentage的索引和data相同,因此可以直接在data檔案上增加一列Percentage:

data["Percentage"]=percentage.values

 

現在data的前10行如下:

        Name Gender  Birth  Year  Percentage
0       Mary      F   7065  1880    0.077643
1       Anna      F   2604  1880    0.028618
2       Emma      F   2003  1880    0.022013
3  Elizabeth      F   1939  1880    0.021309
4     Minnie      F   1746  1880    0.019188
5   Margaret      F   1578  1880    0.017342
6        Ida      F   1472  1880    0.016177
7      Alice      F   1414  1880    0.015540
8     Bertha      F   1320  1880    0.014507
9      Sarah      F   1288  1880    0.014155

 

書中使用的是另外一種方法,在定義apply方法所需的函式時,直接在分類資料上加入計算出的百分比資料,這樣apply方法可以在保證原資料的前提下把分組後計算出的百分比資料與原資料合併:

def add_percent(group):
    birth=group["Birth"]
    sum=group["Birth"].sum()
    group["Percentage"]=birth/sum
    return group

data=by_year_gender.apply(add_percent)

 

但是,我們怎麼確保各組百分比相加之和等於1呢?這時就需要做一個合理性檢查(sanity check)。用numpy的allclose方法來檢視每組百分比之和是否接近1:

import numpy as np
print(np.allclose(data.groupby(["Year","Gender"])["Percentage"].sum(),1))

結果顯示為True。

 

在上面的分析過程中,我們發現這個資料檔案比較大,為了加速分析程序,我們在這裡提取每組(年份+性別)人數最多的前1000名的姓名進行分析:

by_year_gender=data.groupby(["Year", "Gender"])

pieces=[]
for year,group in by_year_gender:
    pieces.append(group.sort_values(by="Birth", ascending=False)[:1000])
top1000=pd.concat(pieces, ignore_index=True)

 

後續的分析都將針對這個top1000資料集。

 

接下來讓我們選取John,Harry,Mary,Marilyn這幾個常見的名字,看一下它們各年份在同性別中佔比分別是多少(時間趨勢),並用折線圖畫出來。

fig,ax=plt.subplots(4,1,figsize=(8,8))

pivot=pd.pivot_table(top1000, values="Percentage", index=["Year", "Gender"], columns="Name")

for i, name, gender in zip(range(5),["John", "Harry", "Mary", "Marilyn"],["M","M","F","F"]):
    ax[i].plot(range(1880,2011),pivot["{}".format(name)].unstack()["{}".format(gender)],label="{}".format(name))
    ax[i].legend()

plt.show()

首先,因為選取了4個名字,所以我們把影象分成4個分圖。然後,按照年份和性別分組,顯示各個名字的百分比,用名字作為列,這樣可以很方便地選取資料。最後畫圖。

 

影象如下:

 

書上用的是這幾個名字各年份的總出生人數:

fig,ax=plt.subplots(4,1,figsize=(8,8))

pivot=pd.pivot_table(top1000, values="Birth", index="Year", columns="Name", aggfunc="sum")

for i, name in zip(range(5),["John", "Harry", "Mary", "Marilyn"]):
    ax[i].plot(range(1880,2011),pivot["{}".format(name)],label="{}".format(name))
    ax[i].legend()

plt.show()

 

總之,看起來常見的名字越來越不受歡迎了。那麼到底是不是這樣呢?讓我們來驗證一下。

 

把提取出來的排名前1000的名字每年的總佔比畫出來,如果總佔比越來越少,那就說明使用其他名字的比例越來越高(名字越來越多樣化)。

import numpy as np

total_percentage=pd.pivot_table(top1000, values="Percentage", index="Year", columns="Gender", aggfunc="sum")

ax.plot(range(1880,2011),total_percentage["F"], label="Female")
ax.plot(range(1880,2011),total_percentage["M"], label="Male")
ax.set_yticks(np.linspace(0,1.2,13))
ax.set_xticks(range(1880,2011,10))
ax.set_xlim(1880,2010)

ax.legend()

plt.show()

 

還有一種辦法是計算總出生人數前50%的不同姓名的數量,如果數量在增加,就說明名字越來越多樣化。

 

這個資料不是那麼容易弄出來,讓我們先以2010年男寶寶的名字為例,來算一下。

boy2010=top1000[(top1000["Gender"]=="M") & (top1000["Year"]==2010)]
boy2010=boy2010.sort_values(by="Percentage", ascending=False)

boy2010["Percent_Cumsum"]=boy2010["Percentage"].cumsum()
num=boy2010["Percent_Cumsum"].searchsorted(0.5)

首先,提取2010年性別為男的資料,然後按照百分比進行排序,接下來在百分比這一列上使用累計函式cumsum。那麼哪裡才是百分比累計到50%的地方呢?答案是用searchsorted方法。num顯示為116,由於索引是從0開始,因此前117個名字佔總出生人數的前50%。

 

接下來,我們把這一方法擴充套件到top1000的總體資料上。首先定義apply方法所需的函式,再用apply方法把函式用於整個資料。

def get_percentile_num(group,p=0.5):
    group=group.sort_values(by="Percentage", ascending=False)
    percent_cumsum=group["Percentage"].cumsum()
    num=percent_cumsum.searchsorted(p)
    return num+1

by_year_gender=top1000.groupby(["Year", "Gender"])
percentile_num=by_year_gender.apply(get_percentile_num)

percentile_num=percentile_num.unstack()
percentile_num["F"]=percentile_num["F"].astype(int)
percentile_num["M"]=percentile_num["M"].astype(int)

做完以上這兩步後再把資料展開,並轉換資料型別。

 

現在percentile_num的前10行顯示如下:

Gender   F   M
Year          
1880    38  14
1881    38  14
1882    38  15
1883    39  15
1884    39  16
1885    40  16
1886    41  16
1887    41  17
1888    42  17
1889    43  18

 

接下來就可以畫圖了:

ax.plot(range(1880,2011),percentile_num["F"],label="Female")
ax.plot(range(1880,2011),percentile_num["M"],label="Male")
ax.legend()

plt.show()

這兩種方法都證明隨著年份增加,名字越來越多樣化了,而且女性的名字多樣化程度更甚。

 

最後一個字母的變革:有研究表明,近百年來,男孩名字的最後一個字母在數量分佈上發生了顯著變化。那麼到底是不是這樣呢?

 

首先提取男孩的名字資料,並在此資料上增加一列:Last_Letter,顯示名字的最後一個字母。然後生成一張透視表,按最後一個字母分類,顯示每個字母每年在男性名字中的佔比。最後,用柱形圖畫出來。

boy=top1000[top1000["Gender"]=="M"]

boy["Last_Letter"]=boy["Name"].apply(lambda x: x[-1])

letters=pd.pivot_table(boy, values="Percentage", index="Last_Letter", columns="Year", aggfunc="sum")

fig,ax=plt.subplots(figsize=(12,6))

i=0
for year in [1910,1960,2010]:
    ax.bar([j+i for j in range(26)],letters[year],label="{}".format(year), width=-0.3, align="edge")
    i+=0.3
ax.legend()
ax.set_xticks(range(26))
ax.set_xticklabels(letters.index.values)

plt.show()

 

影象如下:

可以看出,最後一個字母為n的男名比例越來越高了。

 

現在選取尾字母為"d","n","y"的男孩名字的百分比,並繪製折線圖,這樣就可以看出其隨時間變化的趨勢了。

 

由於我們需要選取字母,因此將letters透視表進行轉置,這樣就變成了字母為列,年份為行。letters.T的前5行顯示如下:

Last_Letter         a         b         c         d         e         f  \
Year                                                                      
1880         0.006842  0.004516  0.003159  0.083010  0.121664  0.000932   
1881         0.007613  0.004665  0.003285  0.083247  0.123139  0.000824   
1882         0.006623  0.004407  0.003070  0.084900  0.127658  0.001056   
1883         0.007139  0.004272  0.002858  0.084066  0.125831  0.001013   
1884         0.007095  0.004247  0.002744  0.085639  0.126803  0.001145   

Last_Letter         g         h         i   j    ...      q         r  \
Year                                             ...                    
1880         0.001285  0.036554  0.001810 NaN    ...    NaN  0.067326   
1881         0.001449  0.037380  0.002045 NaN    ...    NaN  0.072190   
1882         0.001240  0.036688  0.001821 NaN    ...    NaN  0.070043   
1883         0.001290  0.037465  0.001596 NaN    ...    NaN  0.071680   
1884         0.001311  0.036978  0.001381 NaN    ...    NaN  0.078055   

Last_Letter         s         t         u         v         w         x  \
Year                                                                      
1880         0.166735  0.062755  0.000181  0.000299  0.007629  0.002751   
1881         0.162495  0.061818  0.000258  0.000179  0.007424  0.002650   
1882         0.160265  0.062109  0.000088  0.000378  0.007653  0.003079   
1883         0.158116  0.064359  0.000105  0.000421  0.007589  0.002657   
1884         0.154327  0.060973  0.000087  0.000315  0.007217  0.002997   

Last_Letter         y         z  
Year                             
1880         0.075398  0.000262  
1881         0.077451  0.000079  
1882         0.076966  0.000229  
1883         0.079096  0.000115  
1884         0.079532  0.000236  

[5 rows x 26 columns]

 

接下來用d,n,y的資料分別畫折線圖:

for letter in ["d","n","y"]:
    ax.plot(letters.T.index.values, letters.T[letter], label=letter)
ax.set_xlim(1880,2010)
ax.legend()

plt.show()

 

男名變女名,女名變男名

 

有的名字本來是男孩取用的多,但是隨著時間的改變,漸漸變成了受女孩歡迎的名字。我們以Lesly相關的名字為例,來看一看這個變化。

 

首先把Lesly相關的名字提取出來,看看有哪些:

all_names=top1000["Name"].unique()

mask=["lesl" in name.lower() for name in all_names]

lesly_like=all_names[mask]
['Leslie' 'Lesley' 'Leslee' 'Lesli' 'Lesly']

這裡提取了lesl開頭的名字,一共有5種。

 

在top1000資料集裡提取這5種名字的資料,然後做一個透視表。

filtered=top1000[top1000["Name"].isin (lesly_like)]

table=pd.pivot_table(filtered, values="Birth", index="Year", columns="Gender", aggfunc="sum")

 

在透視表裡增加一列:當年男女出生人數的總和。

table["Sum"]=table["F"]+table["M"]

 

現在透視表的前5行如下:

Gender     F      M    Sum
Year                      
1880     8.0   79.0   87.0
1881    11.0   92.0  103.0
1882     9.0  128.0  137.0
1883     7.0  125.0  132.0
1884    15.0  125.0  140.0

 

按每年男女出生人數比例畫圖:

ax.plot(table.index.values, table["F"]/table["Sum"], "-", label="Female")
ax.plot(table.index.values, table["M"]/table["Sum"], "--", label="Male")

ax.legend()

plt.show()

 

影象如下:

由此可以看出,Lesly相關的名字由男名變成了女名。