一道基礎(chǔ)題,多種解題思路,引出Pandas多個(gè)知識(shí)點(diǎn)
小小明:「凹凸數(shù)據(jù)」專欄作者,Pandas數(shù)據(jù)處理高手,致力于幫助無(wú)數(shù)數(shù)據(jù)從業(yè)者解決數(shù)據(jù)處理難題。
源于林胖發(fā)出的一道基礎(chǔ)題:

基礎(chǔ)解法explode函數(shù)
這道題最簡(jiǎn)單的解法,相信大部分用過(guò)pandas的朋友都會(huì),林胖也馬上發(fā)出了自己的答案:
import?pandas?as?pd
mydict?=?{'A':?[1],?'B':?[2,?3],?'C':?[4,?5,?6]}
pd.DataFrame(mydict.items()).explode(1)
結(jié)果:

詳解
mydict.items()是python基礎(chǔ)字典的內(nèi)容,它返回了這個(gè)字典鍵值對(duì)組成的元組列表:
mydict.items()
返回:
dict_items([('A',?[1]),?('B',?[2,?3]),?('C',?[4,?5,?6])])
將這個(gè)內(nèi)部是元組的可迭代對(duì)象傳入DataFrame的構(gòu)造函數(shù)中:
pd.DataFrame(mydict.items())
返回結(jié)果:

這是pandas最基礎(chǔ)的開(kāi)篇知識(shí)點(diǎn)使用可迭代對(duì)象構(gòu)造DataFrame,列表的每個(gè)元素都是整個(gè)DataFrame對(duì)應(yīng)的一行,而這個(gè)元素內(nèi)部迭代出來(lái)的每個(gè)元素將構(gòu)成DataFrame的某一列。
然后再看看這個(gè)explode函數(shù),它是pandas 0.25版本才出現(xiàn)的函數(shù),只有一個(gè)參數(shù)可以傳入列名,然后該函數(shù)就可以把該列的列表每個(gè)元素?cái)U(kuò)展到多行上。
效果與hive使用lateral view+explode實(shí)現(xiàn)的效果幾乎一致,類似于:
select?a,b_i?from?df?lateral?view?explode(b)?tmp?as?b_i;
可以參考很早之前的一篇文章:https://blog.csdn.net/as604049322/article/details/105985770
沒(méi)有exlode函數(shù)如何解決這個(gè)問(wèn)題
但是,黃佬說(shuō)版本太低沒(méi)有這個(gè)函數(shù),于是我給群友們出了一道題:

在黃佬的邀請(qǐng)下,一位經(jīng)過(guò)我多次輔導(dǎo)的群友率先使用了循環(huán)法解題:

我覺(jué)得非常棒,但我也希望看到有人再用變形法實(shí)現(xiàn)一次。林胖和一位群友再次給出了簡(jiǎn)化版本的循環(huán)解法:

經(jīng)過(guò)一番提示后,小五哥和林胖終于給出了變形法的解法:

非常不錯(cuò),群友們終于獨(dú)立的多思路解決了這個(gè)問(wèn)題,真的要撒花呀!?。?/p>
下面我們?cè)敿?xì)分析一下,循環(huán)法和變形法的解法吧:
循環(huán)法解題
基本寫(xiě)法:
result?=?[]
for?k,?vs?in?mydict.items():
????for?v?in?vs:
????????result.append((k,?v))
pd.DataFrame(result)
本質(zhì)上就是實(shí)現(xiàn)了一個(gè)笛卡爾積的拉平操作,將mydict.items這個(gè)可迭代對(duì)象的元組構(gòu)造笛卡爾積并按照整體拉平。
上面的基本寫(xiě)法,應(yīng)該99%以上的朋友都能看懂,但 林胖 的循環(huán)簡(jiǎn)化解法:
import?itertools
result?=?[]
for?k,?v?in?mydict.items():
????result.extend(itertools.product(k,?v))
pd.DataFrame(result)
部分朋友可能沒(méi)有看明白,這個(gè)就需要查詢一下product方法的官方文檔(https://docs.python.org/zh-cn/3.7/library/itertools.html?highlight=product#itertools.product):
product(*iterables,?repeat=1)?-->?product?object
參數(shù):
iterables 為可迭代對(duì)象 可選參數(shù)repeat 表示重復(fù)次數(shù)
用于生成可迭代對(duì)象輸入的笛卡兒積,相當(dāng)于生成器表達(dá)式中的嵌套循環(huán)。
例如:product(A, B) 中的元素A和B將共同構(gòu)成可迭代元素[A, B]作為iterables傳入和 ((x,y) for x in A for y in B) 返回結(jié)果一樣。
返回示例:
product(‘a(chǎn)b’, range(3)) --> (‘a(chǎn)’,0) (‘a(chǎn)’,1) (‘a(chǎn)’,2) (‘b’,0) (‘b’,1) (‘b’,2) product((0,1), (0,1), (0,1)) --> (0,0,0) (0,0,1) (0,1,0) (0,1,1) (1,0,0) …
也可以傳入可選參數(shù) repeat 表示重復(fù)的次數(shù):例如,product(A, repeat=4) 和 product(A, A, A, A) 的返回結(jié)果是一樣的。
列表的extend方法是將可迭代對(duì)象的每個(gè)元素都添加到列表中,而append方法只能添加單個(gè)元素。
當(dāng)然,我們還可以將整個(gè)for循環(huán)改寫(xiě)成列表生成式:
result?=?[(k,?v)?for?k,?vs?in?mydict.items()?for?v?in?vs]
pd.DataFrame(result)
也可以簡(jiǎn)化代碼量。
變形法解題
df?=?pd.DataFrame(mydict.items(),?columns=["a",?"b"])
df
實(shí)現(xiàn)思路,上面的界面是下面最左邊:

列表分列的2種方法
列表分列的思路:Pandas的Series對(duì)象調(diào)用apply方法單個(gè)元素返回的結(jié)果是Series時(shí),這個(gè)Series的每個(gè)數(shù)據(jù)會(huì)作為Datafrem的每一列,索引會(huì)作為列名。
對(duì)Series進(jìn)行列表分列
例如:
df["b"].apply(pd.Series)
結(jié)果:

不過(guò)這樣會(huì)丟失原本的"a"列,我們可以先將"a"列設(shè)置為索引,再進(jìn)行Series分列操作:
df.set_index("a")["b"].apply(pd.Series)
或者把結(jié)果設(shè)置成原本的"a"列為索引:
df["b"].apply(pd.Series).set_index(df["a"])
結(jié)果均為上述實(shí)現(xiàn)思路的第二步。
直接對(duì)Datafream進(jìn)行列表分列
如果我們希望直接使用Datafream實(shí)現(xiàn)分列可以借助agg方法,因?yàn)閍gg方法是對(duì)每一列的Series對(duì)象操作:
df.agg({"a":?lambda?x:?x,?"b":?pd.Series})
結(jié)果:

但這操作導(dǎo)致列多了一個(gè)級(jí)別,需要?jiǎng)h除:
df.agg({"a":?lambda?x:?x,?"b":?pd.Series}).droplevel(0,?axis=1)
結(jié)果:

只要再執(zhí)行set_index("a"):
df.agg({"a":?lambda?x:?x,?"b":?pd.Series}).droplevel(0,?axis=1).set_index("a")
結(jié)果就會(huì)與實(shí)現(xiàn)思路的第二步結(jié)果一致。
將字典的鍵作為索引的2種讀取方法
當(dāng)然上面我只是為了給大家講述分列的一些方法。對(duì)于這個(gè)例子,其實(shí)我們可以直接通過(guò)pd.DataFrame.from_dict方法orient參數(shù)傳入’index’,直接獲得第二步的結(jié)果(只是索引沒(méi)有名稱):
df?=?pd.DataFrame.from_dict(mydict,?'index')
或者分別傳入data和索引index:
df?=?pd.DataFrame(data=mydict.values(),?index=mydict.keys())
都能得到以下結(jié)果:

melt實(shí)現(xiàn)逆透視
說(shuō)起逆透視我個(gè)人首先想到了melt方法,然后才想到melt方法實(shí)現(xiàn)的本質(zhì)用到了stack方法。
為了避免索引丟失,我們首先還原索引為普通的列:
df?=?df.rename_axis(index="a").reset_index()
df
結(jié)果:

然后使用melt方法進(jìn)行逆透視:
df.melt(id_vars='a',?value_name='b')
結(jié)果:

然后刪除第二列,再刪除空值行,再將數(shù)值列轉(zhuǎn)換為整數(shù)類型就搞定。
最終代碼:
df?=?pd.DataFrame.from_dict(mydict,?'index')
df?=?df.melt(id_vars='a',?value_name='b').drop(columns="variable").dropna()
df.b?=?df.b.astype("int")
df
成功得到結(jié)果:

stack實(shí)現(xiàn)逆透視
df?=?pd.DataFrame.from_dict(mydict,?'index')
df.stack()
結(jié)果:
A??0????1.0
B??0????2.0
???1????3.0
C??0????4.0
???1????5.0
???2????6.0
dtype:?float64
結(jié)果返回了一個(gè)多級(jí)索引的Series,我們首先需要?jiǎng)h除索引中多余的部分:
df.stack().droplevel(1)
結(jié)果:
A????1.0
B????2.0
B????3.0
C????4.0
C????5.0
C????6.0
dtype:?float64
此時(shí)我們?cè)龠€原索引到普通列:
df.stack().droplevel(1).reset_index()
再重新設(shè)置一下列名:
df.stack().droplevel(1).reset_index().set_axis(["a",?"b"],?axis=1)
最后重設(shè)一下B列的類型:
df.b?=?df.b.astype("int")
最終代碼:
df?=?pd.DataFrame.from_dict(mydict,?'index')
df?=?df.stack().droplevel(1).reset_index().set_axis(["a",?"b"],?axis=1)
df.b?=?df.b.astype("int")
df
結(jié)果:

歡迎你在下方評(píng)論區(qū)留言,發(fā)表你的看法,給大家分享和互動(dòng)!
如果大家喜歡我的文章,請(qǐng)動(dòng)動(dòng)你的小手,點(diǎn)個(gè)贊吧~
我們的文章到此就結(jié)束啦,如果你喜歡今天的Python 實(shí)戰(zhàn)教程,請(qǐng)持續(xù)關(guān)注Python實(shí)用寶典。
有任何問(wèn)題,可以在公眾號(hào)后臺(tái)回復(fù):加群,回答相應(yīng)紅字驗(yàn)證信息,進(jìn)入互助群詢問(wèn)。
原創(chuàng)不易,希望你能在下面點(diǎn)個(gè)贊和在看支持我繼續(xù)創(chuàng)作,謝謝!
點(diǎn)擊下方閱讀原文可獲得更好的閱讀體驗(yàn)
Python實(shí)用寶典?(pythondict.com)
不只是一個(gè)寶典
歡迎關(guān)注公眾號(hào):Python實(shí)用寶典
