說明:本文章爲Python數據處理學習日誌,主要內容來自書本《利用Python進行數據分析》,Wes McKinney著,機械工業出版社。
“以我的觀點來看,如果只需要用Python進行高效的數據分析工作,根本就沒必要非得成爲通用軟件編程方面的專家不可。”——作者
接下來是書本一些代碼的實現,用來初步瞭解Python處理數據的功能,相關資源可在下方鏈接下載。
書本相關資源
讀取文件第一行
相關例子可以再shell中執行,也可以再Canopy Editor中執行。以下代碼在Canopy Editor中執行。
path='E:\\Enthought\\book\\ch02\\usagov_bitly_data2012-03-16-1331923249.txt'
open(path).readline()
在shell中執行則不要轉置符’\’,path爲存放文件路徑。在shell中執行一定要用pylab模式打開,不然繪圖會出現些小問題。
path='E:\Enthought\book\ch02\usagov_bitly_data2012-03-16-1331923249.txt'
輸出結果:
Out[2]: '{ "a": "Mozilla\\/5.0 (Windows NT 6.1; WOW64) AppleWebKit\\/535.11 (KHTML, like Gecko) Chrome\\/17.0.963.78 Safari\\/535.11", "c": "US", "nk": 1, "tz": "America\\/New_York", "gr": "MA", "g": "A6qOVH", "h": "wfLQtf", "l": "orofrog", "al": "en-US,en;q=0.8", "hh": "1.usa.gov", "r": "http:\\/\\/www.facebook.com\\/l\\/7AQEFzjSi\\/1.usa.gov\\/wfLQtf", "u": "http:\\/\\/www.ncbi.nlm.nih.gov\\/pubmed\\/22415991", "t": 1331923247, "hc": 1331822918, "cy": "Danvers", "ll": [ 42.576698, -70.954903 ] }\n'
以上結果爲純文本加載。
Python中的內置或者第三方模塊將JSON字符串轉化爲Python字典對象。這裏,將使用json模塊及其loads函數逐行加載已經下載好的數據文件:
import json
path='E:\\Enthought\\book\\ch02\\usagov_bitly_data2012-03-16-1331923249.txt'
records=[json.loads(line) for line in open(path)]
records[0]
這樣,records對象就成了一組Python字典了。
Out[6]:
{u'a': u'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 Safari/535.11',
u'al': u'en-US,en;q=0.8',
u'c': u'US',
u'cy': u'Danvers',
u'g': u'A6qOVH',
u'gr': u'MA',
u'h': u'wfLQtf',
u'hc': 1331822918,
u'hh': u'1.usa.gov',
u'l': u'orofrog',
u'll': [42.576698, -70.954903],
u'nk': 1,
u'r': u'http://www.facebook.com/l/7AQEFzjSi/1.usa.gov/wfLQtf',
u't': 1331923247,
u'tz': u'America/New_York',
u'u': u'http://www.ncbi.nlm.nih.gov/pubmed/22415991'}
現在,只要一字符串形式給出想要訪問的鍵就可以得到當前記錄中相應的值了:
records[0]['tz']
Out[7]: u'America/New_York'
print records[0]['tz']
America/New_York
對時區進行計數
1.用純Python對時區進行計數
假設我們想知道該數據集中最常出現的是哪個時區(即tz字段)。首先,我們用列表推導式取出一組時區:
time_zones=[rec['tz'] for rec in records if 'tz' in rec]
time_zones[:10] #獲取前10條記錄的tz
結果:
Out[12]:
[u'America/New_York',
u'America/Denver',
u'America/New_York',
u'America/Sao_Paulo',
u'America/New_York',
u'America/New_York',
u'Europe/Warsaw',
u'',
u'',
u'']
接下來對時區進行計數:
def get_counts(sequence):
counts={}
for x in sequence:
if x in counts:
counts[x]+=1
else:
counts[x]=1
return counts
或者使用Python標準庫
def get_counts2(sequence):
counts=defaultdict(int) #所有初始值均會初始化爲0
for x in sequence:
counts[x]+=1
return counts
調用函數
counts = get_counts(time_zones)
counts['America/New_York']
Out[20]: 1251
len(time_zones)
Out[21]: 3440
如果只想要得到前10位的時區及其計數值,我們需要用到一些關羽字典的處理技巧:
def top_counts(count_dict,n=10):
value_key_pairs=[(count,tz) for tz,count in count_dict.items()]
value_key_pairs.sort()
return value_key_pairs[-n:]
數據處理結果
top_counts(counts)
Out[26]:
[(33, u'America/Sao_Paulo'),
(35, u'Europe/Madrid'),
(36, u'Pacific/Honolulu'),
(37, u'Asia/Tokyo'),
(74, u'Europe/London'),
(191, u'America/Denver'),
(382, u'America/Los_Angeles'),
(400, u'America/Chicago'),
(521, u''),
(1251, u'America/New_York')]
或者在Python標準庫中找到collections.Counter類,處理起來就比較簡單:
from collections import Counter
counts=Counter(time_zones)
counts.most_common(10)
Out[29]:
[(u'America/New_York', 1251),
(u'', 521),
(u'America/Chicago', 400),
(u'America/Los_Angeles', 382),
(u'America/Denver', 191),
(u'Europe/London', 74),
(u'Asia/Tokyo', 37),
(u'Pacific/Honolulu', 36),
(u'Europe/Madrid', 35),
(u'America/Sao_Paulo', 33)]
2.用pandas對時區進行計數
DataFrame是pandas中重要的數據結構,它用於將數據表示成一個表格。
from pandas import DataFrame,Series
import pandas as pd;import numpy as np
frame = DataFrame(records)
結果
frame['tz'][:10]
Out[34]:
0 America/New_York
1 America/Denver
2 America/New_York
3 America/Sao_Paulo
4 America/New_York
5 America/New_York
6 Europe/Warsaw
7
8
9
Name: tz, dtype: object
frame[‘tz’]所返回的Series對象有一個value_counts方法,該方法可以讓我們得到所需要的信息:
tz_counts = frame['tz'].value_counts()
tz_counts[:10]
Out[36]:
America/New_York 1251
521
America/Chicago 400
America/Los_Angeles 382
America/Denver 191
Europe/London 74
Asia/Tokyo 37
Pacific/Honolulu 36
Europe/Madrid 35
America/Sao_Paulo 33
Name: tz, dtype: int64
繪圖
然後,我們可以利用繪圖庫(即matplotlib)爲該段數據生成一張圖片。爲此,我們先給記錄中位未知或者缺失的時區填上一個替代值。fillna函數可以替換缺失值(NA),而未知值(空字符串)則可以通過布爾型數組索引加以替換:
clean_tz=frame['tz'].fillna('Missing')
clean_tz[clean_tz==''] = 'Unknown'
tz_counts=clean_tz.value_counts()
tz_counts[:10]
Out[41]:
America/New_York 1251
Unknown 521
America/Chicago 400
America/Los_Angeles 382
America/Denver 191
Missing 120
Europe/London 74
Asia/Tokyo 37
Pacific/Honolulu 36
Europe/Madrid 35
Name: tz, dtype: int64
繪圖
tz_counts[:10].plot(kind='barh',rot=0)
Out[42]: <matplotlib.axes._subplots.AxesSubplot at 0xe50b1d0>
結果
對Agent(‘a’)進行劃分
‘a’字段含有執行URL縮短操作的瀏覽器、設備、應用程序的相關信息:
frame['a'][0]
Out[43]: u'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 Safari/535.11'
運用Python內置的字符串函數和正則表達式,可以將這種字符串的第一節(與瀏覽器大致對應)分離出來並得到另外一份用戶行爲摘要:
results = Series([x.split()[0] for x in frame.a.dropna()]) #按空格切分字段'a'
results[:5]
Out[45]:
0 Mozilla/5.0
1 GoogleMaps/RochesterNY
2 Mozilla/4.0
3 Mozilla/5.0
4 Mozilla/5.0
dtype: object
results.value_counts()[:8]
Out[46]:
Mozilla/5.0 2594
Mozilla/4.0 601
GoogleMaps/RochesterNY 121
Opera/9.80 34
TEST_INTERNET_AGENT 24
GoogleProducer 21
Mozilla/6.0 5
BlackBerry8520/5.0.0.681 4
dtype: int64
現在,根據Windows用戶和非Windows用戶對時區統計信息進行分解。爲了方便起見,嘉定只要agent字符串中含有“Windows”就認爲該用戶爲Windows用戶。
cframe = frame[frame.a.notnull()] #刪除agent爲空的記錄
operating_system = np.where(cframe['a'].str.contains('Windows'),'Windows','Not Windows')
operating_system[:5]
Out[52]: #可能是版本原因,此處略有不同,原書以frame形式輸出
array(['Windows', 'Not Windows', 'Windows', 'Not Windows', 'Windows'],
dtype='|S11')
接下來就可以根據時區和新得到的操作系統列表對數據進行分組,然後通過size對分組結果進行計數(類似於上面的value_counts函數),並利用unstack對技術結果進行重塑:
by_tz_os = cframe.groupby(['tz',operating_system])
agg_counts = by_tz_os.size().unstack().fillna(0)
agg_counts[:10]
Out[56]:
Not Windows Windows
tz
245.0 276.0
Africa/Cairo 0.0 3.0
Africa/Casablanca 0.0 1.0
Africa/Ceuta 0.0 2.0
Africa/Johannesburg 0.0 1.0
Africa/Lusaka 0.0 1.0
America/Anchorage 4.0 1.0
America/Argentina/Buenos_Aires 1.0 0.0
America/Argentina/Cordoba 0.0 1.0
America/Argentina/Mendoza 0.0 1.0
最後,選出最常出現的時區。根據agg_counts中的行數構造一個間接索引數組:
indexer = agg_counts.sum(1).argsort()
indexer[:10]
Out[58]:
tz
24
Africa/Cairo 20
Africa/Casablanca 21
Africa/Ceuta 92
Africa/Johannesburg 87
Africa/Lusaka 53
America/Anchorage 54
America/Argentina/Buenos_Aires 57
America/Argentina/Cordoba 26
America/Argentina/Mendoza 55
dtype: int64
然後通過take按照這個順序截取最後10行:
count_subset = agg_counts.take(indexer)[-10:]
count_subset
Out[60]:
Not Windows Windows
tz
America/Sao_Paulo 13.0 20.0
Europe/Madrid 16.0 19.0
Pacific/Honolulu 0.0 36.0
Asia/Tokyo 2.0 35.0
Europe/London 43.0 31.0
America/Denver 132.0 59.0
America/Los_Angeles 130.0 252.0
America/Chicago 115.0 285.0
245.0 276.0
America/New_York 339.0 912.0
生成圖形
count_subset.plot(kind='barh',stacked=True)
Out[61]: <matplotlib.axes._subplots.AxesSubplot at 0xeb89d68>
結果
爲了看清楚小分組中Windows用戶的相對比例,可以將各行規範化爲“總計爲1”並重新繪圖:
normed_subset = count_subset.div(count_subset.sum(1),axis=0)
normed_subset.plot(kind='barh',stacked=True)
Out[63]: <matplotlib.axes._subplots.AxesSubplot at 0xed1e1d0>
結果