DataFrame高階操作:如何進(jìn)行行列轉(zhuǎn)換

轉(zhuǎn)自 Pandas與數(shù)據(jù)整理

在 Tidy Data 論文中,Wickham 博士 提出了這樣一種“整潔”的數(shù)據(jù)結(jié)構(gòu):每個(gè)變量是一列,每次觀測(cè)結(jié)果是一行,不同的觀測(cè)類型存放在單獨(dú)的表中。他認(rèn)為這樣的數(shù)據(jù)結(jié)構(gòu)可以幫助分析師更簡(jiǎn)單高效地進(jìn)行處理、建模、和可視化。他在論文中列舉了 五種 不符合整潔數(shù)據(jù)的情況,并演示了如何通過(guò) R 語(yǔ)言 對(duì)它們進(jìn)行整理。本文中,我們將使用 Python 和 Pandas 來(lái)達(dá)到同樣的目的。

文中的源代碼和演示數(shù)據(jù)可以在 GitHub(鏈接)上找到。讀者應(yīng)該已經(jīng)安裝好 Python 開(kāi)發(fā)環(huán)境,推薦各位使用 Anaconda 和 Spyder IDE。

列名稱是數(shù)據(jù)值,而非變量名

import pandas as pd
df = pd.read_csv('data/pew.csv')
df.head(10)
1.png

表中的列“<10k”、“10-20k”其實(shí)是“收入”變量的具體值。變量 是指某一特性的觀測(cè)值,如身高、體重,本例中則是收入、宗教信仰。表中的數(shù)值數(shù)據(jù)構(gòu)成了另一個(gè)變量——人數(shù)。要做到 每個(gè)變量是一列 ,我們需要進(jìn)行以下變換:

df = df.set_index('religion')
df = df.stack()
df.index = df.index.rename('income', level=1)
df.name = 'frequency'
df = df.reset_index()
df.head(10)
2.png

這里我們使用了 Pandas 多級(jí)索引的 stack / unstack 特性。stack() 會(huì)將列名轉(zhuǎn)置為新一級(jí)的索引,并將數(shù)據(jù)框(DataFrame)轉(zhuǎn)換成序列(Series)。轉(zhuǎn)置后,我們對(duì)行和列的名稱做一些調(diào)整,再用 reset_index() 將數(shù)據(jù)框還原成普通的二維表。

除了使用多級(jí)索引,Pandas 還提供了另一種更為便捷的方法——melt()。該方法接收以下參數(shù):

  • frame: 需要處理的數(shù)據(jù)框;
  • id_vars: 保持原樣的數(shù)據(jù)列;
  • value_vars: 需要被轉(zhuǎn)換成變量值的數(shù)據(jù)列;
  • var_name: 轉(zhuǎn)換后變量的列名;
  • value_name: 數(shù)值變量的列名。
df = pd.read_csv('data/pew.csv')
df = pd.melt(df, id_vars=['religion'], value_vars=list(df.columns)[1:],
             var_name='income', value_name='frequency')
df = df.sort_values(by='religion')
df.to_csv('data/pew-tidy.csv', index=False)
df.head(10)

這段代碼會(huì)輸出相同的結(jié)果,下面的示例中我們都將使用 melt() 方法。我們?cè)賮?lái)看另外一個(gè)案例:

3.png

在這個(gè)數(shù)據(jù)集中,每周的排名都被記錄到了不同的數(shù)據(jù)列中。如果我們想要回答“Dancing Queen 這首歌在 2000年7月15日 的排名如何”,就需要結(jié)合 date.entered 字段做一些運(yùn)算才行。下面我們來(lái)對(duì)這份數(shù)據(jù)進(jìn)行整理:

df = pd.read_csv('data/billboard.csv')
df = pd.melt(df, id_vars=list(df.columns)[:5], value_vars=list(df.columns)[5:],
             var_name='week', value_name='rank')
df['week'] = df['week'].str[2:].astype(int)
df['date.entered'] = pd.to_datetime(df['date.entered']) + pd.to_timedelta((df['week'] - 1) * 7, 'd')
df = df.rename(columns={'date.entered': 'date'})
df = df.sort_values(by=['track', 'date'])
df.to_csv('data/billboard-intermediate.csv', index=False)
df.head(10)
4.png

上述代碼中,我們還將 date.entered 轉(zhuǎn)換成了每一周的具體日期,week 字段也作為單獨(dú)的數(shù)據(jù)列進(jìn)行存儲(chǔ)。但是,我們會(huì)在表中看到很多重復(fù)的信息,如歌手、曲名等,我們將在第四節(jié)解決這個(gè)問(wèn)題。

一列包含多個(gè)變量

人們之所以會(huì)將變量值作為列名,一方面是這樣的表示方法更為緊湊、可以在一頁(yè)中顯示更多信息,還有一點(diǎn)是這種格式便于做交叉驗(yàn)證等數(shù)據(jù)分析工作。下面的數(shù)據(jù)集更是將性別和年齡這兩個(gè)變量都放入了列名中:

5.png

m 表示男性(Male),f 表示女性(Female),0-14、15-24 則表示年齡段。進(jìn)行數(shù)據(jù)整理時(shí),我們先用 Pandas 的字符串處理功能截取 sex 字段,再對(duì)剩余表示年齡段的子串做映射處理。

df = pd.read_csv('data/tb.csv')
df = pd.melt(df, id_vars=['country', 'year'], value_vars=list(df.columns)[2:],
             var_name='column', value_name='cases')
df = df[df['cases'] != '---']
df['cases'] = df['cases'].astype(int)
df['sex'] = df['column'].str[0]
df['age'] = df['column'].str[1:].map({
    '014': '0-14',
    '1524': '15-24',
    '2534': '25-34',
    '3544': '35-44',
    '4554': '45-54',
    '5564': '55-64',
    '65': '65+'
})
df = df[['country', 'year', 'sex', 'age', 'cases']]
df.to_csv('data/tb-tidy.csv', index=False)
df.head(10)
6.png

變量存儲(chǔ)在行和列中

下表是一個(gè)名為 MX17004 的氣象站收集的溫度數(shù)據(jù)??梢钥吹剑掌诒环胖迷诹忻?,我們可以用 melt 進(jìn)行處理;tmax 和 tmin 則表示最高溫度和最低溫度,他們很顯然是兩個(gè)不同的變量,用來(lái)衡量單個(gè)觀測(cè)對(duì)象的屬性的,本例中的觀測(cè)對(duì)象是“天”。因此,我們需要使用 unstack 將其拆分成兩列。

7.png
df = pd.read_csv('data/weather.csv')
df = pd.melt(df, id_vars=['id', 'year', 'month', 'element'],
             value_vars=list(df.columns)[4:],
             var_name='date', value_name='value')
df['date'] = df['date'].str[1:].astype('int')
df['date'] = df[['year', 'month', 'date']].apply(
    lambda row: '{:4d}-{:02d}-{:02d}'.format(*row),
    axis=1)
df = df.loc[df['value'] != '---', ['id', 'date', 'element', 'value']]
df = df.set_index(['id', 'date', 'element'])
df = df.unstack()
df.columns = list(df.columns.get_level_values('element'))
df = df.reset_index()
df.to_csv('data/weather-tidy.csv', index=False)
df
8.png

同一表中包含多種觀測(cè)類型

在處理 Billboard 數(shù)據(jù)集時(shí),我們會(huì)看到冗余的曲目信息,這是因?yàn)樵摫韺?shí)際記錄的是兩種不同的觀測(cè)類型——歌曲曲目和周排名。整理時(shí),我們需要先為每首歌曲生成一個(gè)唯一標(biāo)識(shí),即 id,然后拆分到單獨(dú)的表中。

df = pd.read_csv('data/billboard-intermediate.csv')
df_track = df[['artist', 'track', 'time']].drop_duplicates()
df_track.insert(0, 'id', range(1, len(df_track) + 1))
df = pd.merge(df, df_track, on=['artist', 'track', 'time'])
df = df[['id', 'date', 'rank']]
df_track.to_csv('data/billboard-track.csv', index=False)
df.to_csv('data/billboard-rank.csv', index=False)
print(df_track, '\n\n', df)
9.png
10.png

同一觀測(cè)類型分布在不同表中

原始的數(shù)據(jù)集可能會(huì)以兩種方式進(jìn)行了拆分,一種是按照某個(gè)變量拆分,如按年拆分為2000年、2001年,按地理位置拆分為中國(guó)、英國(guó);另一種是按不同的屬性拆分,如一份數(shù)據(jù)是收集溫度的傳感器記錄的,另一份是濕度傳感器,他們記錄的都是每一天的觀測(cè)值。對(duì)于第一種情況,我們可以編寫(xiě)一個(gè)讀取數(shù)據(jù)的函數(shù),遍歷目錄中的文件,并將文件名作為單獨(dú)的列加入數(shù)據(jù)框,最后使用 pd.concat 進(jìn)行合并;第二種情況則要求數(shù)據(jù)集中的記錄有一個(gè)唯一標(biāo)識(shí),如日期、身份證號(hào),并通過(guò) pd.merge 將各個(gè)數(shù)據(jù)集聯(lián)系起來(lái)。

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Python 和 Pandas 數(shù)據(jù)分析教程 原文:Data Analysis with Python and P...
    布客飛龍閱讀 83,384評(píng)論 9 225
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 32,275評(píng)論 2 89
  • 你是一灘清泉, 凈化我骯臟的靈魂; 你是一抹亮光, 讓迷茫的我找到方向; 你是高高在上的天使, 我卻是卑微的生命;...
    少女心暖暖閱讀 238評(píng)論 0 1
  • 2017年9月11日,弟子蔡小敏,種種子第18天。 發(fā)心:我今天不僅是為了我個(gè)人而聞思修,更是為了六道輪回一切如母...
    Rubywry閱讀 161評(píng)論 0 2
  • 從廣東來(lái)到江蘇實(shí)習(xí)9個(gè)月了,是結(jié)束回去,還是該繼續(xù)? 還沒(méi)想到以后要做什么,回去還是一片迷茫,從頭來(lái)? 還是要休先...
    Summe_Chen閱讀 278評(píng)論 0 0

友情鏈接更多精彩內(nèi)容