SOTON私人定制:利用Python進(jìn)行數(shù)據(jù)分析(分組運(yùn)算)

數(shù)據(jù)分析比較常見的步驟是將對數(shù)據(jù)集進(jìn)行分組然后應(yīng)用函數(shù)低千,這步也可以稱之為分組運(yùn)算。Hadley Wickham大神為此創(chuàng)造了一個專用術(shù)語“split-apply-combine"箱吕,即拆分-應(yīng)用-合并。那么當(dāng)我們談?wù)?strong>分組運(yùn)算的時候柿冲,我們其實在談?wù)撌裁茨兀?/p>

  • Splitting:根據(jù)標(biāo)準(zhǔn)對數(shù)據(jù)進(jìn)行拆分分組
  • Applying: 對每組都分別應(yīng)用一個函數(shù)
  • Combining: 將結(jié)果合并新的數(shù)據(jù)結(jié)構(gòu)

分組運(yùn)算一般要求的數(shù)據(jù)存放格式為“長格式”茬高,所以先介紹“長格式”和“寬格式”的轉(zhuǎn)換,然后是分組運(yùn)算的具體操作假抄。

“長格式“ VS”寬格式“

數(shù)據(jù)的常見保存方式有兩種怎栽,長格式(long format)或是寬格式(wide format)

  • wide format
基因 分生組織
gene1 582 91 495
gene2 305 3505 33

在寬格式下,類別型變量單獨成列宿饱。如上的植物的不同部位分為3列熏瞄,列中的數(shù)據(jù)表示為表達(dá)量∶裕看起來就非常的直觀强饮,而且日常生活中也是按照如此方法記錄數(shù)據(jù)。

  • long format
基因 組織 表達(dá)量
gene1 分生組織 582
gene2 分生組織 305
gene1 91
gene2 3503
gene1 492
gene2 33

而長結(jié)構(gòu)的數(shù)據(jù)則是類別型變量定義為專門的一列为黎。通常情況下邮丰,關(guān)系型數(shù)據(jù)庫(如MySQL)通常以長格式存儲數(shù)據(jù),這是因為固定架構(gòu)下隨著表中數(shù)據(jù)的增加或刪除铭乾, 類別行(如組織)能夠增加或減少剪廉。

后續(xù)的分組操作其實更傾向于數(shù)據(jù)是以長格式進(jìn)行保存,所以這里先介紹pandas是如何進(jìn)行長格式和寬格式之間的轉(zhuǎn)換的炕檩。

首先創(chuàng)建一個用于測試的長格式數(shù)據(jù)斗蒋,方法如下:

import pandas.util.testing as tm; tm.N = 3
def unpivot(frame):
    N, K = frame.shape
    data = {'value' : frame.values.ravel('F'),
            'variable' : np.asarray(frame.columns).repeat(N),
            'date' : np.tile(np.asarray(frame.index), K)}
    return pd.DataFrame(data, columns=['date', 'variable', 'value'])
ldata = unpivot(tm.makeTimeDataFrame())

然后把長格式變成寬格式,可認(rèn)為是把數(shù)據(jù)的列“旋轉(zhuǎn)” 為行

# 第一種方法: pivot
# pd.pivot(index, columns, values), 對應(yīng)索引笛质,類別列和數(shù)值列
pivoted = ldata.pivot('date','variable','value')
# 第二種方法: unstack
## 先用set_index建立層次化索引
unstacked = ldata.set_index(['date','varibale']).unstack('variable')

再把寬格式變?yōu)殚L格式泉沾,也可以認(rèn)為是把數(shù)據(jù)的行“旋轉(zhuǎn)”成列。

# 先把unstacked數(shù)據(jù)還原成普通的DataFrame
wdata = nstacked.reset_index()
wdata.columns = ['date','A','B','C','D']
# 第一種方法:melt
pd.melt(wdata, id_vars=['date'])
# 第二種方法: stack
# 如果原始數(shù)據(jù)沒有索引妇押,需要用set_index重建
wdata.set_index('date').stack()

GroupBy

第一步是將數(shù)據(jù)集根據(jù)一定標(biāo)準(zhǔn)跷究,即分組鍵,拆分多個組舆吮,pandas提供了grouby方法揭朝,能夠根據(jù)如下形式的分組鍵工作:

  • 列表和數(shù)組(Numpy array)队贱,其長度與待分組的軸一致
  • DataFrame的某個列名的值
  • 字典或Series色冀,提供了待分組軸上的值與分組名(label -> group name)之間的對應(yīng)關(guān)系
  • 根據(jù)多層次的軸索引
  • Python函數(shù)潭袱,用于軸索引或索引中的各個標(biāo)簽

下面舉例說明:
形式一:根據(jù)某一列的值,一般是類別型數(shù)值

df = DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
                          'foo', 'bar', 'foo', 'foo'],
                  'B' : ['one', 'one', 'two', 'three',
                          'two', 'two', 'one', 'three'],
                   'C' : np.random.randn(8),
                   'D' : np.random.randn(8)})
# 根據(jù)A列進(jìn)行拆分
df.groupby('A')
# 根據(jù)A和B列進(jìn)行拆分
df.groupby(['A','B'])

這會產(chǎn)生一個pandas.core.groupby.DataFrameGroupBy object對象锋恬,沒有進(jìn)行實質(zhì)性的運(yùn)算操作屯换,只產(chǎn)生了中間信息。你可以查看分組之后每一組的大小

df.groupby(['A','B']).size()

形式二: 根據(jù)函數(shù). 下面定義了一個函數(shù)根據(jù)列名是否屬于元音進(jìn)行拆分

def get_letter_type(letter):
    if letter.lower() in 'aeiou':
        return 'vowel'
    else:
        return 'consonant'

grouped = df.groupby(get_letter_type, axis=1).

形式三: 根據(jù)層次索引与学。 不同于R語言data.frame只有一個行或列名彤悔,pandas的DataFrame允許多個層次結(jié)構(gòu)的行或列名。

# 構(gòu)建多層次索引的數(shù)據(jù)
arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
           ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
index = pd.MultiIndex.from_arrays(arrays, names=['first', 'second'])
s = pd.Series(np.random.randn(8), index=index)
# 根據(jù)不同層次的索引進(jìn)行分組
level1 = s.groupby(level='first') # 第一層
level2 = s.groupby(level=1) # 第二層

形式四: 根據(jù)字典或Series進(jìn)行分組
可以通過字典定義分組信息的映射索守。

mapping = {'A':'str','B':'str','C':'values','D':'values','E':'values'}
df.groupby(mapping, axis=1)

形式四: 同時根據(jù)行索引和列數(shù)值進(jìn)行分組

df.index = index
df.groupby([pd.Grouper(level='first'),'A']).mean()

分組對象迭代: 上面產(chǎn)生的GroupBy分組對象都是可以進(jìn)行迭代晕窑,會產(chǎn)生一個一組二元元組。

# 對于單鍵值
for name, group in df.groupby(['A']):
    print(name)
    print(group)
# 對于多個鍵值
for (k1,k2), group in df.groupby(['A']):
    print(k1,k2)
    print(group)

選擇分組:我們可以在分組結(jié)束后用get_group()提取某個特定的組

df.groupby('A').get_group('foo')
# 下面是結(jié)果
                A      B         C         D         E
first second                                          
bar   one     foo    one  0.709603 -1.023657 -0.321730
baz   one     foo    two  1.495328 -0.279928 -0.297983
foo   one     foo    two  0.495288 -0.563845 -1.774453
qux   one     foo    one  1.649135 -0.274208  1.194536
      two     foo  three -2.194127  3.440418 -0.023144
# 多個組
df.groupby(['A', 'B']).get_group(('bar', 'one'))

Apply

分組步驟較為直接卵佛,而應(yīng)用函數(shù)步驟則變化較多杨赤。這一步,我們可能會做如下操作:

  • 數(shù)據(jù)聚合(Aggregation):分別計算各組的統(tǒng)計信息截汪,如均值疾牲,大小等
  • 轉(zhuǎn)換(Transformation): 對每一組進(jìn)行特異性的計算,如標(biāo)準(zhǔn)化衙解,缺失值插值等
  • 過濾(Filtration): 根據(jù)統(tǒng)計信息進(jìn)行篩選阳柔,舍棄部分組。

數(shù)據(jù)聚合:Aggregation

所謂的聚合蚓峦,也就是數(shù)組產(chǎn)生標(biāo)量值的數(shù)據(jù)轉(zhuǎn)換過程舌剂。我們可以使用mean, count, min, sum等一般性方法,也可以用GroupBy對象創(chuàng)建后的aggregate或等價的agg方法暑椰。

grouped = df.groupby('A')
grouped.mean()
# 等價于
grouped.agg(np.mean)

對于多個分組而言架诞,會產(chǎn)生層次索引的結(jié)果,如果希望層次索引成為單獨一列的話干茉,需要在groupby用到as_index選項谴忧。或者最后使用reset_index方法

# as_index
grouped = df.groupby(['A','B'], as_index=False)
grouped.agg(np.mean)
# reset_index
df.groupby(['A','B']).sum().reset_index()

aggregate或等價的agg還允許對每一列使用多個統(tǒng)計函數(shù)

df.groupby('A').agg([np.sum,np.mean,np.std])
# 語法糖 df.groupby('A').C 等價于
# grouped=df.groupby(['A'])
# grouped['C']
df.groupby('A').C.agg([np.sum,np.mean,np.std])

或者是不同列使用不同函數(shù)角虫,比如說對D列計算標(biāo)準(zhǔn)差沾谓,對列求平均

grouped=df.groupby(['A'])
grouped.agg({'D': np.std , 'C': np.mean})

注:目前sum, mean, std和sem方法對Cython進(jìn)行了優(yōu)化。

轉(zhuǎn)換: Transformation

transform返回的對象和原來分組數(shù)據(jù)具有相同的大小戳鹅。轉(zhuǎn)換所用的函數(shù)必須滿足如下條件:

  • 返回的結(jié)果大小要與原先的組塊(group chunk)一致
  • 在組塊中逐列操作
  • 并非在組塊上原位運(yùn)算均驶。原先的組塊被認(rèn)為是不可修改的,任何對原來組塊的修改可能會導(dǎo)致意想不到的結(jié)果枫虏。如果你用到了fillna妇穴,必須是grouped.transform(lambda x: x.fillna(inplace=False))

舉例說明爬虱,比如說我們相對每一組數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化。當(dāng)然我們先得有一組數(shù)據(jù), 隨機(jī)生成一組從1999年到2002年腾它,然后以100天為一個滑窗(window)跑筝,計算每一個滑窗的均值(rolling)。

# date_range產(chǎn)生日期索引
index = pd.date_range('10/1/1999', periods=1100)
ts = pd.Series(np.random.normal(0.5, 2, 1100), index)
ts = ts.rolling(window=100,min_periods=100).mean().dropna()

然后要根據(jù)年份進(jìn)行分組瞒滴,對各組中的數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化曲梗,計算zscore.

# 兩個匿名函數(shù),用于分組和計算zscore
key = lambda x : x.year
zscore = lambda x : (x - x.mean())/x.std()
transformed = ts.groupby(key).transform(zscore)

應(yīng)用zscore的過程中使用了廣播(broadcast)技術(shù)妓忍。讓我們可視化一下轉(zhuǎn)換前后數(shù)據(jù)形狀

compare = pd.DataFrame({'Original': ts, 'Transformed': transformed})
compare.plot()
轉(zhuǎn)換前后

過濾:Filtration

過濾返回原先數(shù)據(jù)的子集虏两。比如說上面時間周期數(shù)據(jù),過濾掉均值小于0.55的年份世剖。

ts.groupby(key).filter(lambda x : x.mean() > 0.55)

Apply: 更加靈活的“拆分-應(yīng)用-合并”

上述的aggregatetransform都有一定的局限性定罢,傳入的函數(shù)只能有兩個結(jié)果,要么是產(chǎn)生一個可以廣播的標(biāo)量值旁瘫,要么是產(chǎn)生相同大小的數(shù)組祖凫。apply是一個更加一般化的函數(shù),能完成上面所描述的所有任務(wù)境蜕,還能做得更好蝙场。

  • 分組返回描述性統(tǒng)計結(jié)果
df.groupby('A').apply(lambda x : x.describe())
# 效果等價于df.groupby('A').describe()
apply
  • 修改返回的分組名
def f(group):
     return pd.DataFrame({'original' : group,
                          'demeaned' : group - group.mean()})
df.groupby('A')['C'].apply(f)
              demeaned  original
first second                    
bar   one     0.278558  0.709603
      two     0.280707 -0.140075
baz   one     1.064283  1.495328
      two    -0.848109 -1.268891
foo   one     0.064243  0.495288
      two     0.567402  0.146620
qux   one     1.218089  1.649135
      two    -2.625172 -2.194127

更多有用的技巧如獲取每一組的第n行,見官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粱年,一起剝皮案震驚了整個濱河市售滤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌台诗,老刑警劉巖完箩,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拉队,居然都是意外死亡弊知,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門粱快,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秩彤,“玉大人,你說我怎么就攤上這事事哭÷祝” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵鳍咱,是天一觀的道長降盹。 經(jīng)常有香客問我,道長谤辜,這世上最難降的妖魔是什么蓄坏? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任价捧,我火速辦了婚禮,結(jié)果婚禮上涡戳,老公的妹妹穿的比我還像新娘结蟋。我一直安慰自己,他們只是感情好妹蔽,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布椎眯。 她就那樣靜靜地躺著挠将,像睡著了一般胳岂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舔稀,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天乳丰,我揣著相機(jī)與錄音,去河邊找鬼内贮。 笑死产园,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夜郁。 我是一名探鬼主播什燕,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼竞端!你這毒婦竟也來了屎即?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤事富,失蹤者是張志新(化名)和其女友劉穎技俐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體统台,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡雕擂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贱勃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片井赌。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贵扰,靈堂內(nèi)的尸體忽然破棺而出仇穗,到底是詐尸還是另有隱情,我是刑警寧澤拔鹰,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布仪缸,位于F島的核電站,受9級特大地震影響列肢,放射性物質(zhì)發(fā)生泄漏恰画。R本人自食惡果不足惜宾茂,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拴还。 院中可真熱鬧跨晴,春花似錦、人聲如沸片林。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽费封。三九已至焕妙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弓摘,已是汗流浹背焚鹊。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留韧献,地道東北人末患。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像锤窑,于是被迫代替她去往敵國和親璧针。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容