我來做數據--如何對數據進行處理以滿足機器學習技術(一):嬰兒名字頻率數據

標簽(空格分隔): python 數據處理


SSA 提供了一份從1880 到 2010 年的嬰兒名字頻率數據。我們主要用這份數據來做以下的處理:

  • 計算指定名字(可以是你自己的,也可以是別人的)的年度比例。
  • 計算某個名字的相對排名。
  • 計算各年度最流行的名字,以及增長或減少最快的名字。
  • 分析名字趨勢:元音、輔音、長度、總體多樣性、拼寫變化、首尾字母等
  • 分析外源性趨勢:圣經中的名字、名人、人口結構變化等。

1、數據是非常標準的以逗號隔開的格式,所以可以用Pandas.read_csv將其加載到Dataframe中:

>>> names1880 = pd.read_csv('G:\\lcw\\names\\yob1880.txt',names=['name','sex','births'])
#這里的names 是用來作為dataframe的表頭
>>> names1880 
           name sex  birth
0          Mary   F   7065
1          Anna   F   2604
2          Emma   F   2003
3     Elizabeth   F   1939
4        Minnie   F   1746
5      Margaret   F   1578
6           Ida   F   1472
7         Alice   F   1414
8        Bertha   F   1320
9         Sarah   F   1288

用births列的sex分組小計表示該年度的births總計:

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

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

>>> years = range(1880,2011)
>>> pieces = []
>>> columns = ['name','sex','births']
>>> for year in years:
    path = 'G:\\lcw\\names\\yob%d.txt' % year
    frame = pd.read_csv(path,names = columns)
    frame['year'] = year
    pieces.append (frame)
>>> names = pd.concat(pieces,ignore_index=True)
>>> names
              name 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
5         Margaret   F    1578  1880
6              Ida   F    1472  1880
7            Alice   F    1414  1880
8           Bertha   F    1320  1880
9            Sarah   F    1288  1880
10           Annie   F    1258  1880
11           Clara   F    1226  1880
12            Ella   F    1156  1880
13        Florence   F    1063  1880
14            Cora   F    1045  1880
15          Martha   F    1040  1880
16           Laura   F    1012  1880
17          Nellie   F     995  1880
18           Grace   F     982  1880
19          Carrie   F     949  1880
20           Maude   F     858  1880
21           Mabel   F     808  1880
22          Bessie   F     794  1880
23          Jennie   F     793  1880
24        Gertrude   F     787  1880
25           Julia   F     783  1880
26          Hattie   F     769  1880
27           Edith   F     768  1880
28          Mattie   F     704  1880
29            Rose   F     700  1880
...            ...  ..     ...   ...
1690754    Zaviyon   M       5  2010
1690755   Zaybrien   M       5  2010
1690756   Zayshawn   M       5  2010
1690757     Zayyan   M       5  2010
1690758       Zeal   M       5  2010
1690759     Zealan   M       5  2010
1690760   Zecharia   M       5  2010
  • 合并數據集(pandas.merge 和 pandas.concat)
  • pandas.merge 可根據一個或多個鍵將不同 DataFrame 中的行連接起來
  • pandas.concat 可以沿著一條軸將多個對象堆疊到一起
  • combine_first 可以用一個對象中的值填充另一個對象中對應位置的缺失值

使用鍵參數的DataFrame進行合并
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'), copy=True) 用于通過一個或多個鍵將兩個數據集的行連接起來,類似于 SQL 中的 JOIN。該函數的典型應用場景是,針對同一個主鍵存在兩張包含不同字段的表,現在我們想把他們整合到一張表里。在此典型情況下,結果集的行數并沒有增加,列數則為兩個元數據的列數和減去連接鍵的數量。

on=None 用于顯示指定列名(鍵名),如果該列在兩個對象上的列名不同,則可以通過 left_on=None, right_on=None 來分別指定。或者想直接使用行索引作為連接鍵的話,就將 left_index=False, right_index=False 設為 True。

how='inner' 參數指的是當左右兩個對象中存在不重合的鍵時,取結果的方式:inner 代表交集;outer 代表并集;left 和 right 分別為取一邊。

suffixes=('_x','_y') 指的是當左右對象中存在除連接鍵外的同名列時,結果集中的區分方式,可以各加一個小尾巴。

對于多對多連接,結果采用的是行的笛卡爾積。
軸向連接
merge 算是一種整合的話,軸向連接 pd.concat() 就是單純地把兩個表拼在一起,這個過程也被稱作連接(concatenation)、綁定(binding)或堆疊(stacking)。

因此可以想見,這個函數的關鍵參數應該是 axis,用于指定連接的軸向。在默認的 axis=0 情況下,pd.concat([obj1,obj2]) 函數的效果與 obj1.append(obj2) 是相同的;而在 axis=1 的情況下,pd.concat([df1,df2],axis=1) 的效果與 pd.merge(df1,df2,left_index=True,right_index=True,how='outer') 是相同的。可以理解為 concat 函數使用索引作為“連接鍵”。

本函數的全部參數為:pd.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False, keys=None, levels=None, names=None, verify_integrity=False)。

  • objs 就是需要連接的對象集合,一般是列表或字典;axis = 0 是連接軸向
  • join = 'outer' 參數作用于當另一條軸的 index 不重疊的時候,只有'inner' 和 'outer' 可選
    (這里相當于 mysql 里面的內聯和外聯)
  • join_axes=None 參數用于詳細制定其他軸上使用的索引,優先級可以覆蓋 join 參數,join_axes 的類型是一個列表,其中的元素為其他軸的 index .
  • keys=None 參數的作用是在結果集中對源數據進行區分。前例中可以看到,結果集中的項無法區分來源,因此使用一個列表型的 keys 參數可以在連接軸上創建一個層次化索引;另一個隱式使用 keys 參數的方法是傳入 objs 參數時使用字典,字典的鍵就會被當做 keys。
  • verify_integrity=False 參數用于檢查結果對象新連接軸上的索引是否有重復項,有的話引發 ValueError,可以看到這個參數的作用與 ignore_index 是互斥的。
    (如果 ignore_index = True ,則意味著index不能是重復的,而 = False ,則意味著index可以是重復的)
>>> df1 = DataFrame({'a':range(3),'b':range(3)})
>>> df2 = DataFrame({'a':range(4)})
>>> pd.concat([df1,df2])
   a   b
0  0   0
1  1   1
2  2   2
0  0 NaN
1  1 NaN
2  2 NaN
3  3 NaN
[7 rows x 2 columns]
>>> pd.concat([df1,df2],join='inner',ignore_index=True)
   a
0  0
1  1
2  2
3  0
4  1
5  2
6  3
[7 rows x 1 columns]
  • ps:concat 默認是按行將多個DataFrame組合到一起的;必須指定ignore_index=True,因為我們不希望保留read_csv所返回的原始行號。

2、利用groupby 或 pivot_table在year和sex級別上對其進行聚合了:

>>> total_births = names.pivot_table('births',rows = 'year',cols='sex',aggfunc=sum)
>>> total_births
sex         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
1890   190377   111026
1891   185486   101198
1892   212350   122038
1893   212908   112319
1894   222923   115775
1895   233632   117398

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

>>> def add_prop(group):
    births = group.births.astype(float)
    # 由于births是整數,所以我們在計算分式時必須將分子或分母轉換成浮點數
    group['prop']= births / births.sum()
    return group
>>> names = names.groupby(['year','sex']).apply(add_prop)
>>> names
              name 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
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
10           Annie   F    1258  1880  0.013825
11           Clara   F    1226  1880  0.013474
    

檢查各個分組總計值是否足夠近似于1

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

取出數據的一個子集:每對sex/year組合的前1000個名字。又是一個分組操作:

>>> def get_top1000(group):
    return group.sort_index(by='births',ascending=False)[: 1000]
>>> grouped = names.groupby(['year','sex'])
>>> top1000 = grouped.apply(get_top1000)
>>> top1000
                       name 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

同樣功能的代碼:

pieces = []
for year,group in names.groupby(['year','sex']):
    pieces.append(group.sort_index(by='births',ascending=False)[: 1000])
top1000 = pd.concat(pieces, ignore_index=True)

生成得到了完整的數據集和top1000數據集,我們就可以開始分析各種命名趨勢了。首先分成男女兩個部分:

boys = top1000[top1000.sex == 'M']
girls = top1000[top1000.sex == 'F']
#首先,稍作整理即可繪制出相應的圖表(比如每年叫做john和Mary的嬰兒數)首先生成一張按照year和name統計的總出生數透視表:
total_births = top1000.pivot_table('births', rows='year', cols='name', aggfunc=sum)
>>> subset = total_births[['John','Harry','Mary','Marilyn']]
>>> subset.plot(subplots=True,figsize=(12,10),grid=False,title="Number of births per year")
>>> from pylab import *
>>> show()

從跳出的圖例中可以看出,父母愿意給小孩起常見名字的越來越少,這個可以從數據中得到驗證。一個方法就是計算最流行的1000個名字所占的比例,按year和sex進行聚合并繪圖:

table = top1000.pivot_table('prop',rows='year',cols='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))

另一個方法是計算占總出生人數前50%的不同名字的數量,我們只考慮2010年男孩的數量:

df = boys[boys.year == 2010]

用numpy 以一種更聰明的矢量方式。先計算prop的累計和cumsum,然后再通過searchsorted方法找出0.5應該被插入到哪個位置才能保證不破壞順序:

>>> prop_cumsum = df.sort_index(by='prop',ascending=False).prop.cumsum()
>>> prop_cumsum[:10]
year  sex         
2010  M    1676644    0.011523
           1676645    0.020934
           1676646    0.029959
           1676647    0.038930
           1676648    0.047817
           1676649    0.056579
           1676650    0.065155
           1676651    0.073414
           1676652    0.081528
           1676653    0.089621
Name: prop, dtype: float64
>>> prop_cumsum.searchsorted(0.5)
array([116], dtype=int64)
  • numpy.searchsorted 是一個在有序數組上執行二分查找的數組方法,只要將值插入到它返回的那個位置就能維持數組的有序性。
  • 由于數組索引是從0開始的,因此我們要給這個結果加1,即最終結果為117.

現在就可以對所有year/sex組合執行這個計算了,按這兩個字段進行groupby處理,然后用一個函數計算各分組的這個值:

>>> def get_quantile_count(group, q=0.5):
    group = group.sort_index(by='prop',ascending=False)
    return group.prop.cumsum().searchsorted(q) +1

>>> diversity = top1000.groupby(['year','sex']).apply(get_quantile_count)
>>> diversity = diversity.unstack('sex')
>>> diversity.head()
sex      F     M
year            
1880  [38]  [14]
1881  [38]  [14]
1882  [38]  [15]
1883  [39]  [15]
1884  [39]  [16]
>>> diversity.plot(title="Number of popular names in top 50%")

3、了解最后一個字母上發生的變化,為了了解具體的情況,首先將全部出生數據在年度、性別以及末字母上進行了聚合:

get_last_letter = lambda x: x[-1]
last_letters = names.name.map(get_last_letter)
last_letters.name = 'last_letter'
table = names.pivot_table('births',rows=last_letters,cols=['sex','year'],aggfunc=sum)
>>> subtable = table.reindex(columns = [1910,1960,2010],level='year')
#選出具有一定代表性的三年,并輸出前面幾行:
>>> subtable = table.reindex(columns = [1910,1960,2010],level='year' )
>>> subtable.head()
sex               F                      M                
year           1910    1960    2010   1910    1960    2010
last_letter                                               
a            108376  691247  670605    977    5204   28438
b               NaN     694     450    411    3912   38859
c                 5      49     946    482   15476   23125
d              6750    3729    2607  22111  262112   44398
e            133569  435013  313833  28655  178823  129012
>>> 
  • python 支持一種有趣的語法,允許你快速定義單行的最小函數。這些叫做lambda的函數,是從Lisp借用來的,可以用在任何需要函數的地方。
>>> g = lamdba x:x*2
>>> g(3)
6
>>> (lambda x: x*2)(3)
6

1 這是一個 lambda 函數,完成同上面普通函數相同的事情。注意這里的簡短的語法:在參數列表周圍沒有括號,而且忽略了 return 關鍵字 (隱含存在,因為整個函數只有一行)。而且,該函數沒有函數名稱,但是可以將它賦值給一個變量進行調用。
2 使用 lambda 函數時甚至不需要將它賦值給一個變量。這可能不是世上最有用的東西,它只是展示了 lambda 函數只是一個內聯函數。

Python函數式編程——map()、reduce()
1、map(func,seq1[,seq2...]) Python 函數式編程中的map()函數是將func作用于seq中的每一個元素,并用一個列表給出返回值。如果func為None,作用通zip().
當seq只有一個時,將func函數作用于這個seq的每一個元素上,得到一個新的seq。![map.jpg-47kB][1]
舉個例子來說明,(假設我們想要得到一個列表中數字%ia3的余數,那么可以寫成下面的代碼):

>>> print map(lambda x:x%3, range(6))
>>> [0,1,2,0,1,2]
>>> print [x%3 for x in range(6)]
>>> [0,1,2,0,1,2]

當seq多于一個時,map都可以并行地對每個seq執行如下圖所示的過程:
![map2.jpg-53.2kB][2]
下面的例子是求兩個列表對應元素的積,可以想象,這是一種可能會經常出現的狀況,而如果不是用map的話,就要使用一個for循環,依次對每個位置執行該函數。

>>> print map(lambda x,y:x*y,[1,2,3],[4,5,6])
>>> [4,5,6]
#下面的代碼不止實現了乘法,也實現了加法,并把積與和放在一個元組中。
>>> print map(lambda x,y:(x*y,x+y),[1,2,3],[4,5,6])
>>> [(4,5),(10,7),(18,9)]
#當func是None的時候,目的是將多個列表相同的位置的元素歸并到一個元組,在現在已經有了專用的函數zip()
>>> print map(None,[1,2,3],[4,5,6])
>>> [(1,4),(2.5),(3,6)]
>>> print zip([1,2,3],[4,5,6])
# 需要注意的是,不同長度的seq是無法執行map函數的,會出現類型錯誤

2、reduce( func, seq[, init] ) Python
reduce函數即為化簡,它是這樣一個過程:每次迭代,將上一次的迭代結果(第一次時為init的元素,如沒有init則為seq的第一個元素)與下一個元素一同執行一個二元的func函數。在reduce函數中,init是可選的,如果使用,則作為第一次迭代的第一個元素使用。
簡單來說,一個形象化的例子:

reduce(func,[1,2,3]) = func(func(1,2),3)

![reduce.jpg-44.9kB][3]
舉個例子來說,階乘是一個常見的數學方法,Python中并沒有給出一個階乘的內建函數,我們可以使用reduce實現一個階乘的代碼:

>>> n=5
>>> print reduce(lambda x,y:x*y,range(1,n+1))
>>> 120
#如果想要得到2倍的階乘的值,那么就可以用到init這個可選參數了
m=2
n=5
print 
reduce(lambda x,y:x*y,range(1,n+1),m)

然后通過這個字母比例數據之后,就可以生成一張各年度各性別的條形圖了:

>>> 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')
<matplotlib.axes._subplots.AxesSubplot object at 0x0000000025C8B9E8>
>>> letter_prop['F'].plot(kind='bar',rot=0,ax=axes[1],title='Female',legend=False)
<matplotlib.axes._subplots.AxesSubplot object at 0x0000000025CAC860>
>>> show()

畫圖的各個屬性:
subplots(numRows,numCols,plotNum)
subplot將整個繪圖區域等分為numRows行*numCols列個子區域,然后按照從左到右,從上到下的順序對每個子區域進行編號。subplot在plotNum指定的區域中創建一個軸對象。figsize就是長寬的比例
![figure_1.png-57.4kB][4]
可以看出,從20世紀60年代開始,以字母"n"結尾的男孩名字出現了顯著的增長。最后,回到之前創建的那個table完整表,按年度和性別對其進行規范化處理,并在男孩名字中選取幾個字母,最后進行轉置以便將各個列做成一個時間序列:

>>> table = names.pivot_table('births',rows=last_letters,cols=['sex','year'],aggfunc=sum)
>>> letter_prop = table / table.sum(). astype(float)
>>> dny_ts = letter_prop.ix[['d','n','y'], 'M']. T
#這里加上了索引和轉置。
>>> dny_ts .head()
last_letter         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
dny_ts.plot()

![figure_2.png-48.6kB][5]

4、有趣的趨勢:

早年流行于男孩的名字今年來"變性了",例如Lesley或Leslie。回到top1000數據集,找出其中以"les1"開頭的一組名字

>>> all_names = top1000.name.unique()
>>> mask = np.array(['lesl' in x.lower() for x in all_names])
>>> lesley_like = all_names[mask]
>>> lesley_like
array(['Leslie', 'Lesley', 'Leslee', 'Lesli', 'Lesly'], dtype=object)
>>> 

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

filtered = top1000[top1000.name.isin(lesley_like)]
filtered.groupby('name').births.sum()
  • 不太明白這個地方為什么用sum()
    接下來按性別和年度進行聚合,并按年度進行規范化處理:
>>> table = filtered.pivot_table('births',rows='year',cols='sex',aggfunc='sum')
>>> table = table.div(table.sum(1),axis = 0)
>>> table.tail()
sex   F   M
year       
2006  1 NaN
2007  1 NaN
2008  1 NaN
2009  1 NaN
2010  1 NaN
>>> table.plot(style={'M':'k-','F':'k--'})
<matplotlib.axes._subplots.AxesSubplot object at 0x00000000120D34E0>
>>> show()

這個處理主要用來做什么?暫時還不夠清楚
![figure_4.png-43.5kB][6]
[1]: http://static.zybuluo.com/liuchenwei/g3qk08ljnv55dtxlwnk1q29g/map.jpg
[2]: http://static.zybuluo.com/liuchenwei/bbhyhanlkrat0r2betqika4e/map2.jpg
[3]: http://static.zybuluo.com/liuchenwei/0tguh6zi5d6oy8u1iidgoy05/reduce.jpg
[4]: http://static.zybuluo.com/liuchenwei/84hy9onenin70wlmmdwges8y/figure_1.png
[5]: http://static.zybuluo.com/liuchenwei/hmfql71zdp6m0wxfsq0nnz4o/figure_2.png
[6]: http://static.zybuluo.com/liuchenwei/8qv45iwzzkt6qgqxgh7qe3dv/figure_4.png

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容