前言
資料以json的格式進行存儲,例如通過網絡爬蟲時,那些異步存儲的資料往往都是json類型的;再如企業資料庫中的日志資料,也會以json的格式存放。前不久,一位網友就碰到了這個問題,手中Excel存儲的資料并不是标準化的結構資料,而是以json格式存儲在Excel的每個單元格。那今天我們就來聊聊如何利用Python将半結構化的json資料轉換成結構化資料。
簡單的json格式
json的格式與Python中的字典非常類似,資料放在大括号({})内,每一個元素都是鍵值對,元素之間以逗号隔開。我們都知道,在Python中,是可以将一個字典對象轉換成資料框的,接下來我們就通過一個簡單的例子慢慢進入複雜的環境。
# 加載第三方包import pandas as pd # 資料處理包import numpy as np # 數值計算包import json # json檔案轉換包# 一個簡單的json格式字元串string1 = '{"name":"Sim","gender":"Male","age":28,"province":"江蘇"}'string2 = '{"name":"Lily","gender":"Feale","age":25,"province":"湖北"}'# 檢視資料類型type(string)# 将json格式轉換為字典dict1 = json.loads(string1)
dict2 = json.loads(string2)
type(dict1)
json資料實際上是字典型的字元串,可以直接通過json包中的loads函數完成由字元型到字典型的轉化。那如何根據這兩個字典,組裝成一個2行3列的資料框呢?隻需借助于pandas子產品中的DataFrame函數即可:
# 将字典資料轉換為資料框pd.DataFrame([dict1,dict2])

這裡需要注意的是,上面的字典,是一個鍵僅對應一個值的情況,如果直接将dict1傳遞給DataFrame函數是會出錯的,除非你指定索引值。是以,當你有兩個及以上的這種字典時,你是可以傳遞給DataFrame函數的,但必須以可疊代的形式(如清單、元組、序列等)。還有一種字典,是一個鍵對應多個值,如果是這樣的字典,就可以直接将字典扔給DataFrame函數形成資料框了:
string = '{"name":["Sim","Lily"],"gender":["Male","Feale"],\
"age":[28,25],"province":["江蘇","湖北"]}'# 轉換為資料框 pd.DataFrame(json.loads(string))
盡管這樣完成了一個字典到資料框的轉換,但千萬注意,如果一個字典的鍵包含多個值,那一定要保證所有鍵對應的值個數一緻!OK,了解了這個基礎知識點後,我們來兩個案例,加深一下對知識點的了解。
經典案例一
現在的問題是,如何将表中UserBasic一列拆解出來,即所有鍵值對轉換成變量名和觀測值。
data1 = pd.read_excel(r'C:\Users\Administrator\Desktop\data1.xlsx')
data1.head()
data1.UserBasic[0]
從上面的回報結果來看,表中UserBasic字段的單元格存儲的json字元串都是一個鍵僅對應一個值,這跟上面介紹的string1和string2是一緻的,故如果需要轉換成資料框的話,需要将這些轉換的字典存放到清單中。具體操作如下:
# UserBasic列中的資訊拆分到各個變量中basic = []for i in data1.UserBasic:
basic.append(json.loads(i))
UserBasic = pd.DataFrame(basic)
UserBasic.head()
上面通過循環的方式将UserBasic字段的每一行解析成字典,并儲存到清單中,最後通過DataFrame函數完成資料框的轉換。接下來需要将拆分出來的這列,與原始表中的Id變量,Mobile變量整合到一起。
# 資料整合到一起final_data = pd.concat([data1[['Id','Mobile']],UserBasic], axis = 1)
final_data.head()
效果呈現還是蠻好的,但是有一點不好的是,通過for循環來完成畢竟不是高效的,如果資料量特别大,上百萬行的話,那就得循環執行上百萬次,會耗很多時間。這裡我們借助于apply方法,避免顯式的循環:
# 避免循環的方式trans_data = pd.DataFrame(data1.UserBasic.apply(json.loads))# 資料整合到一起final_data = pd.concat([data1[['Id','Mobile']],trans_data], axis = 1)
經典案例二
現在的問題是在解析字段CellBehaviorData的同時,還要做一次聚合操作(每個使用者ID近3個月的消費平均水準)。
# 讀取資料data2 = pd.read_excel(r'C:\Users\Administrator\Desktop\data2.xlsx')# 檢視字段CellBehaviorData第一行的資訊data2.CellBehaviorData[0]
細心的你一定發現了個問題,這個字元串的起始和結尾并不是大括号({}),而是中括号([]),故接下來要做的第一件事就是去除這兩個中括号;另一方面,behavior鍵對應的值是清單,而且清單中還有多個相同的鍵,如sms_cnt、cell_phone_num等。這樣的json最後形成的資料框一定是多行的,即表中一個單元格會就可以轉換成多行的資料框。不妨,我們先來看一下變量CellBehaviorData第一行形成是資料框張啥樣:
# 通過切片的方式去除首尾的中括号s = data2.CellBehaviorData[0][1:-1]# 将字元串轉換成字典,并取出behavior鍵d = json.loads(s)['behavior']# 将字典轉換為資料框df = pd.DataFrame(d)
df
這就是一行觀測産生的多行資料框,現在的問題是如何将多行的資料框與每一個Id配對上。我們發現字典中除了behavior鍵,還有phone_num鍵,而且該鍵的值是唯一的,正好與上面資料框的cell_phone_num變量比對。是以,待會做資料關聯的時候,就使用phone_num變量和cell_phone_num變量。
# 取出phone_numphone_num = [i['phone_num'] for i in data2.CellBehaviorData.str[1:-1].apply(json.loads)]# 取出CellBehaviorData字段,并解析為資料框df = pd.concat([pd.DataFrame(j) for j in [i['behavior'] for i in data2.CellBehaviorData.str[1:-1].apply(json.loads)]])# 将Id與手機号捆綁user = pd.concat([pd.Series(phone_num,name = 'phone_num'), data2.Id], axis = 1)# 以手機号作為資料的關聯關聯final_data = pd.merge(df, user, left_on = 'cell_phone_num', right_on='phone_num')
final_data.head()# 檢視資料類型final_data.dtypes
為了速度的提升,上面使用了apply技術和清單解析式的技巧将json資料拆解成資料框,同時,發現除Id變量的其他變量類型都是字元串型,需要對數值變量進行轉換,因為接下來還要做聚合操作:
# 挑選需要轉換類型的變量名稱vars = ['call_cnt','call_in_cnt','call_in_time','call_out_cnt','call_out_time','net_flow','sms_cnt','total_amount']# 對以上變量進行資料類型轉換df_convert = final_data[vars].apply(lambda x: x.astype('float'))# 從新完成資料合并final_data2 = pd.concat([df_convert, final_data.loc[:,~final_data.columns.isin(vars)]], axis = 1)# 對每個id計算近三個月的平均名額值stats = final_data2.loc[final_data2.cell_mth.isin(['2017-08','2017-07','2017-06']),:].groupby('Id').aggregate(np.mean)
stats
大功告成,這種類型的資料我們就可以遊刃有餘的完成轉換。但如果CellBehaviorData字段不含有phone_num(鍵)變量的話,如何實作資料關聯呢?這裡把解決方案的代碼呈現出來:
# 構造空清單,存放CellBehaviorData變量每一行形成的資料框final_data = []# 使用zip函數捆綁兩列,并使用for循環for Id,CellBehaviorData in zip(data2.Id, data2.CellBehaviorData): # 組裝資料框
mydf = pd.DataFrame(json.loads(CellBehaviorData[1:-1])['behavior']) # 将資料框與變量Id組裝起來
final_data.append(pd.concat([pd.Series(np.repeat(Id,mydf.shape[0]), name = 'Id'),mydf], axis = 1))# 構造最終的資料框 final_data = pd.concat(final_data)# 資料類型轉換# 挑選需要轉換類型的變量名稱vars = ['call_cnt','call_in_cnt','call_in_time','call_out_cnt','call_out_time','net_flow','sms_cnt','total_amount']# 對以上變量進行資料類型轉換df_convert = final_data[vars].apply(lambda x: x.astype('float'))# 從新完成資料合并final_data2 = pd.concat([df_convert, final_data.loc[:,~final_data.columns.isin(vars)]], axis = 1)
final_data2# 對每個id計算近三個月的平均名額值stats = final_data2.loc[final_data2.cell_mth.isin(['2017-08','2017-07','2017-06']),:].groupby('Id').aggregate(np.mean)
stats
雖然通過上面的方法可以實作資料的關聯和比對,但個人覺得并不是很理想,因為這裡畢竟使用了for循環,一旦資料量大的話,執行起來會比較緩慢,如果高人,還請指點。
結語
OK,今天關于半結構化的json資料轉資料框的分享就介紹到到這裡,希望本篇文章對各位網友有一定的幫助。如果你有任何問題,歡迎在公衆号的留言區域表達你的疑問。歡迎各位朋友繼續轉發與分享文中的内容,讓更多的朋友學習和進步。有關文中的腳本和資料可至下方的連結擷取,再次感謝網友對我的關注和支援。