Python數據分析示例(3)Day4

說明:本文章爲Python數據處理學習日誌,主要內容來自書本《利用Python進行數據分析》,Wes McKinney著,機械工業出版社。

1880-2010年間全美嬰兒姓名

所需文件在Day2中下載,接下來要用到的一些文件的文件格式如下:

yob1880.txt-yob2010.txt
Mary,F,7065
Anna,F,2604
Emma,F,2003

整合數據

可以看到.txt文件中各個記錄字段都以都好‘,’隔開,可以用pandas.read_csv將其加載到DataFrame中:

import pandas as pd
import os
path='E:\\Enthought\\book\\ch02\\names'
os.chdir(path)

names1880 = pd.read_csv('yob1880.txt',names=['name','sex','births'])
names1880[:5]
Out[8]: 
        name sex  births
0       Mary   F    7065
1       Anna   F    2604
2       Emma   F    2003
3  Elizabeth   F    1939
4     Minnie   F    1746

這些文件中僅含有當年出現超過5次的名字。爲簡單起見,可以用births列的sex分組小計表示該年度的births總計:

names1880.groupby('sex').births.sum()
Out[11]: 
sex
F     90993
M    110493
Name: births, dtype: int64

由於該數據集按年度被分隔成多個文件,所以第一件事情就死要將所有數據都組裝到一個DataFrame裏面,並加上一個year字段。使用pandas.connect即可達到這個目的:

years = range(1880,2011)
pieces = []
columns = ['names', 'sex','births']

for year in years:
    path = 'yob%d.txt' % year
    frame = pd.read_csv(path, names=columns)
    frame['year'] = year
    pieces.append(frame)

names = pd.concat(pieces, ignore_index=True)
#將所有數據整合到單個DataFrame數據裏面
names[:5]
Out[25]: 
       names sex  births  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

需要注意的有兩點:

  1. concat默認是按行將多個DataFrame組合到一起。
  2. 必須指定ignore_index=True,因爲我們不希望保留read_csv所返回的原始行號。

分析基本特徵

現在我們得到一個非常大的DataFrame,它包含全部的名字數據。有了這些數據之後,我們就可以利用groupby或pivot_table在year和sex級別上對其進行聚合了:

total_births = names.pivot_table('births',index='year',columns='sex',aggfunc=sum)

total_births.tail() #查詢最後5行數據
Out[36]: 
sex         F        M
year                  
2006  1896468  2050234
2007  1916888  2069242
2008  1883645  2032310
2009  1827643  1973359
2010  1759010  1898382

繪圖:

total_births.plot(title='Total births by sex and year')
Out[37]: <matplotlib.axes._subplots.AxesSubplot at 0x16485d68>

結果:

下面我們來插入一個prop列,用於存放指定名字的嬰兒數相對於總出生數的比例。prop值爲0.02表示每100個嬰兒中有2個取了當前的名字。因此,我們先按year和sex分組,然後再將新列加到哥哥分組上:

def add_prop(group):
    births = group.births
    #births = group.births.astype(float)
    #如果不是python3則要進行類型轉換,因爲整數除法迴向下圓整
    group['prop'] = births/births.sum()
    return group

names = names.groupby(['year','sex']).apply(add_prop)
names[:5]
Out[42]: 
       names sex  births  year      prop
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

在執行這樣的分組處理時,一般都應該做一些有效性檢查,比如驗證所有分組的prop的總和是否爲1。由於這是一個浮點數類型,所以我們用np.allclose來檢查這個分總計值是否足夠近似於(可能不會精確等於)1:

np.allclose(names.groupby(['year','sex']).prop.sum(),1)
Out[46]: True

爲了便於實現進一步的分析,需要有去處該數據的一個子集:每對sex/year組合的前1000個名字。這又是一個分組操作:

 def get_top1000(group):
    return group.sort_values(by='births',ascending=False)[:1000]
#sort_index會出現warning,原因之前已說明

grouped = names.groupby(['year','sex'])
top1000 = grouped.apply(get_top1000)
top1000[:5]
Out[53]: 
                names sex  births  year      prop
year sex                                         
1880 F   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

現在的結果數據集就小多了,接下來的數據分析工作就針對這個top1000數據集了。

分析命名趨勢

有了完整的數據集和剛纔生產的top1000數據集,我們就可以開始分析各種命名趨勢了。首先我們將前1000個名字分爲男女兩個部分:

boys = top1000[top1000.sex=='M']
girls = top1000[top1000.sex=='F']

這是兩個簡單的時間序列,只需要稍作整理即可繪製出相應的圖表(比如每年叫做John和Mary的嬰兒數)。我們先生成一張按year和name統計的總出生數透視表:

total_births = top1000.pivot_table('births',index='year',columns='names',aggfunc=sum)
#因爲之前定義column時屬性設置成了names,後面也跟着用這個了= =

total_births[:5]
Out[65]: 
names  Aaden  Aaliyah  Aarav  Aaron  Aarush  Ab  Abagail  Abb  Abbey  Abbie  \
year                                                                          
1880     NaN      NaN    NaN  102.0     NaN NaN      NaN  NaN    NaN   71.0   
1881     NaN      NaN    NaN   94.0     NaN NaN      NaN  NaN    NaN   81.0   
1882     NaN      NaN    NaN   85.0     NaN NaN      NaN  NaN    NaN   80.0   
1883     NaN      NaN    NaN  105.0     NaN NaN      NaN  NaN    NaN   79.0   
1884     NaN      NaN    NaN   97.0     NaN NaN      NaN  NaN    NaN   98.0   

names  ...    Zoa   Zoe  Zoey  Zoie  Zola  Zollie  Zona  Zora  Zula  Zuri  
year   ...                                                                 
1880   ...    8.0  23.0   NaN   NaN   7.0     NaN   8.0  28.0  27.0   NaN  
1881   ...    NaN  22.0   NaN   NaN  10.0     NaN   9.0  21.0  27.0   NaN  
1882   ...    8.0  25.0   NaN   NaN   9.0     NaN  17.0  32.0  21.0   NaN  
1883   ...    NaN  23.0   NaN   NaN  10.0     NaN  11.0  35.0  25.0   NaN  
1884   ...   13.0  31.0   NaN   NaN  14.0     6.0   8.0  58.0  27.0   NaN

subset = total_births[['John','Harry','Mary','Marilyn']]

subset.plot(subplots=True,figsize=(12,10),grid=False,title="Number of births per year")
Out[68]: 
array([<matplotlib.axes._subplots.AxesSubplot object at 0x0000000033237CC0>,
       <matplotlib.axes._subplots.AxesSubplot object at 0x0000000016085D30>,
       <matplotlib.axes._subplots.AxesSubplot object at 0x000000002EAA6EF0>,
       <matplotlib.axes._subplots.AxesSubplot object at 0x0000000029259048>], dtype=object)

繪製結果:

  • 評估命名多樣性增長
    上圖所反映的境地情況可能意味着父母原意個小孩起常見的名字越來越少。這個假設可以從數據中得到驗證。一個辦法是計算最流行的1000個名字所佔的比例,按year和sex進行聚合並繪圖:
table = top1000.pivot_table('prop',index='year',columns='sex',aggfunc=sum)

table.plot(title="Sum of table1000.prop by year and sex",yticks=np.linspace(0,1.2,13),xticks=range(1880,2020,10))
Out[71]: <matplotlib.axes._subplots.AxesSubplot at 0x2e0dbeb8>

繪製結果:

上圖結果表示,名字的多樣性確實出現增長(前1000項的比例降低)。另一個辦法是計算佔總出生人口前50%的不同名字的數量,這個數字不太好計算。我們只考慮2010年男孩的名字:

df = boys[boys.year==2010]
df[:5]
Out[73]: 
                    names sex  births  year      prop
year sex                                             
2010 M   1676644    Jacob   M   21875  2010  0.011523
         1676645    Ethan   M   17866  2010  0.009411
         1676646  Michael   M   17133  2010  0.009025
         1676647   Jayden   M   17030  2010  0.008971
         1676648  William   M   16870  2010  0.008887

在按prop降序排列後,我們想知道前面多少個名字的人數加起來纔夠50%。雖然編寫一個for循環也能達到目的,但NumPy有更聰明的矢量方法。先計算prop的累計和cumsum,然後通過searchsorted方法找到0.5應該被插在哪個位置才能保證不破壞順序:

prop_cumsum = df.sort_values(by='prop',ascending=False).prop.cumsum()

prop_cumsum[:5]
Out[76]: 
year  sex         
2010  M    1676644    0.011523
           1676645    0.020934
           1676646    0.029959
           1676647    0.038930
           1676648    0.047817
Name: prop, dtype: float64

prop_cumsum.searchsorted(0.5)
Out[77]: array([116], dtype=int64) #注意這裏的返回格式

由於數組索引從0開始,因此我們要給這個結果+1,即最終的結果爲117。現在就對所有year/sex分組執行這個計算了。按這兩個字段進行groupby處理,然後用一個函數計算個分組的這個值:

def get_quantile_count(group,q=0.5):
    group = group.sort_values(by='prop',ascending=False)
    return group.prop.cumsum().searchsorted(0.5)[0]+1
#注意!!!這裏和書本不一樣,上面看到python3的searchsorted()返回的是ndarray類型
#需要先取[0]元素,才能獲得想要的數據,如果不作該處理,繪圖會報錯

diversity = top1000.groupby(['year','sex']).apply(get_quantile_count)
diversity = diversity.unstack('sex')
#依靠sex入棧操作,變Series爲DataFrame

diversity.plot(title="Number of popular names in top 50%")
Out[129]: <matplotlib.axes._subplots.AxesSubplot at 0x218d7cf8>

上面碰到的問題,現在來仔細查看返回的各種類型:

prop_cumsum.searchsorted(0.5)
Out[132]: array([116], dtype=int64)

prop_cumsum.searchsorted(0.5)[0]
Out[133]: 116

type(prop_cumsum.searchsorted(0.5))
Out[134]: numpy.ndarray

type(prop_cumsum.searchsorted(0.5)[0])
Out[135]: numpy.int64

不作上述處理,則會出現下述錯誤:

diversity.plot(title="Number of popular names in top 50%",xticks=range(1880,2020,10))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-95-519f22a5ff7e> in <module>()
----> 1 diversity.plot(title="Number of popular names in top 50%",xticks=range(1880,2020,10))

E:\Enthought\hzk\User\lib\site-packages\pandas\tools\plotting.pyc in __call__(self, kind, ax, figsize, use_index, title, grid, legend, style, logx, logy, loglog, xticks, yticks, xlim, ylim, rot, fontsize, colormap, table, yerr, xerr, label, secondary_y, **kwds)
   3561                            colormap=colormap, table=table, yerr=yerr,
   3562                            xerr=xerr, label=label, secondary_y=secondary_y,
-> 3563                            **kwds)
   3564     __call__.__doc__ = plot_series.__doc__
   3565 

E:\Enthought\hzk\User\lib\site-packages\pandas\tools\plotting.pyc in plot_series(data, kind, ax, figsize, use_index, title, grid, legend, style, logx, logy, loglog, xticks, yticks, xlim, ylim, rot, fontsize, colormap, table, yerr, xerr, label, secondary_y, **kwds)
   2640                  yerr=yerr, xerr=xerr,
   2641                  label=label, secondary_y=secondary_y,
-> 2642                  **kwds)
   2643 
   2644 

E:\Enthought\hzk\User\lib\site-packages\pandas\tools\plotting.pyc in _plot(data, x, y, subplots, ax, kind, **kwds)
   2436         plot_obj = klass(data, subplots=subplots, ax=ax, kind=kind, **kwds)
   2437 
-> 2438     plot_obj.generate()
   2439     plot_obj.draw()
   2440     return plot_obj.result

E:\Enthought\hzk\User\lib\site-packages\pandas\tools\plotting.pyc in generate(self)
   1021     def generate(self):
   1022         self._args_adjust()
-> 1023         self._compute_plot_data()
   1024         self._setup_subplots()
   1025         self._make_plot()

E:\Enthought\hzk\User\lib\site-packages\pandas\tools\plotting.pyc in _compute_plot_data(self)
   1130         if is_empty:
   1131             raise TypeError('Empty {0!r}: no numeric data to '
-> 1132                             'plot'.format(numeric_data.__class__.__name__))
   1133 
   1134         self.data = numeric_data

TypeError: Empty 'DataFrame': no numeric data to plot

原因如下:

diversity.dtypes #這是沒有取[0]的結果
Out[109]: 
sex
F    object #"no numeric data to plot"因爲不是數字類型
M    object #"no numeric data to plot"因爲不是數字類型
dtype: object 

diversity.dtypes #取[0]後均變爲int64
Out[136]: 
sex
F    int64
M    int64
dtype: object

圖像繪製結果:

從上圖中可以看出,女孩的名字的多樣性總是比男孩的高,而且還在越來越高。

  • “最後一個字母”的變革
    2007年,一名嬰兒姓名研究人員Laura Wattenberg在她自己的網站上指出(http://www.babynamewicard.com):近百年來,男孩名字在最後一個字母的分佈發生了顯著的變化。爲了瞭解具體情況,首先將全部出生數據在年度、性別以及末位字母上進行聚合:
get_last_letter = lambda x:x[-1]
last_letters = names.names.map(get_last_letter)
last_letters.names = 'last_letter'

table = names.pivot_table('births',index=last_letters,columns=['sex','year'],aggfunc=sum)

subtable = table.reindex(columns=[1910,1960,2010],level='year')

subtable.head()
Out[143]: 
sex           F                            M                    
year       1910      1960      2010     1910      1960      2010
names                                                           
a      108376.0  691247.0  670605.0    977.0    5204.0   28438.0
b           NaN     694.0     450.0    411.0    3912.0   38859.0
c           5.0      49.0     946.0    482.0   15476.0   23125.0
d        6750.0    3729.0    2607.0  22111.0  262112.0   44398.0
e      133569.0  435013.0  313833.0  28655.0  178823.0  129012.0

接下來,我們需要按總出生數對該表進行規範化處理,以便計算出各性別各末位字母佔總出生人數的比例:

subtable.sum()
Out[144]: 
sex  year
F    1910     396416.0
     1960    2022062.0
     2010    1759010.0
M    1910     194198.0
     1960    2132588.0
     2010    1898382.0
dtype: float64

letter_prop = subtable/subtable.sum() #轉換類型.astype(float)

有了這個字母比例數據後,就可以生成一張各年度各性別的條形圖了:

import matplotlib.pyplot as plt

fig,axes = plt.subplots(2,1,figsize=(10,8))

letter_prop['M'].plot(kind='bar',rot=0,ax=axes[0],title='Male')
Out[149]: <matplotlib.axes._subplots.AxesSubplot at 0x2b7ced30>

letter_prop['F'].plot(kind='bar',rot=0,ax=axes[1],title='Female',legend=False)
Out[150]: <matplotlib.axes._subplots.AxesSubplot at 0x213fd860>

圖像:

從上圖可以看出,從20世紀60年代開始,以字母“n”結尾的男孩子名字出現顯著的增長。回到之前創建的那個完整表,按年度和性別對其進行規範化處理,並在男孩子名字中選出幾個字母,最後進行轉置以便將各個列做成一個時間序列:

letter_prop = table / table.sum()

dny_ts = letter_prop.ix[['d','n','y'],'M'].T

dny_ts.head()
Out[154]: 
names         d         n         y
year                               
1880   0.083055  0.153213  0.075760
1881   0.083247  0.153214  0.077451
1882   0.085340  0.149560  0.077537
1883   0.084066  0.151646  0.079144
1884   0.086120  0.149915  0.080405

有了這個時間序列的DataFrame之後,就可以通過其plot方法繪製出一張趨勢圖了:

dny_ts.plot()
Out[155]: <matplotlib.axes._subplots.AxesSubplot at 0x2b7ce9b0>

趨勢圖:

  • 變成女孩名字的男孩名字(以及相反的情況)
    另一個有趣的趨勢是,早年流行於男孩的名字近年來“變形了”,例如Lesley或Leslie。回到top1000數據集,找出其中以“lesl”開頭的一組名字:
all_names = top1000.names.unique()

mask  =np.array(['lesl' in x.lower() for x in all_names])

lesley_like = all_names[mask]

lesley_like
Out[159]: array(['Leslie', 'Lesley', 'Leslee', 'Lesli', 'Lesly'], dtype=object)

然後利用這個結果過濾其他的名字,並按名字分組計算出生數已查看相對頻率:

filtered = top1000[top1000.names.isin(lesley_like)]

filtered.groupby('names').births.sum()
Out[162]: 
names
Leslee      1082
Lesley     35022
Lesli        929
Leslie    370429
Lesly      10067
Name: births, dtype: int64

接下來,我們按性別和年度進行聚合,並按年度進行規範化處理:

table = filtered.pivot_table('births',index='year',columns='sex',aggfunc=sum)

table = table.div(table.sum(1),axis=0)

table.tail()
Out[172]: 
sex     F   M
year         
2006  1.0 NaN
2007  1.0 NaN
2008  1.0 NaN
2009  1.0 NaN
2010  1.0 NaN

table.plot(style={'M':'k-','F':'k--'})
Out[173]: <matplotlib.axes._subplots.AxesSubplot at 0x2cd089e8>

各年度使用“Lesley型”名字的男女比例:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章