1. 程式人生 > >手把手教你完成半結構化資料的處理

手把手教你完成半結構化資料的處理

前言

      現在越來越多的資料以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,瞭解了這個基礎知識點後,我們來兩個案例,加深一下對知識點的理解。

經典案例一

      先來看一下Excel表中儲存的資料格式,現在的問題是,如何將表中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_num
phone_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))
#這個地方寫點自己看作者文件遇到的一些困難, 主要是repeat函式的用法np.repeat(Id,mydf.shape[0])表示的就是取Id的值然後複製mydf.shape[0]次
# 構造最終的資料框    
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資料轉資料框的分享就介紹到到這裡,希望本篇文章對各位網友有一定的幫助。如果你有任何問題,歡迎在公眾號的留言區域表達你的疑問。歡迎各位朋友繼續轉發與分享文中的內容,讓更多的朋友學習和進步。有關文中的指令碼和資料可至下方的連結獲取,再次感謝網友對我的關注和支援。

關注“每天進步一點點2015

相關材料下載連結

連結: https://pan.baidu.com/s/1kVzNnFp 密碼: tdkm