1. 程式人生 > 其它 >【Python環境】《Python資料科學入門》試譯 第一章 簡介

【Python環境】《Python資料科學入門》試譯 第一章 簡介

“資料!資料!資料!”他焦急地高叫著,“(如果沒有資料),巧婦難為無米之炊啊!” --Arthur Conan Doyle

資料力量

我們正生活在一個被資料淹沒的世界。各大網站都會記錄每一個訪問者的每一次點選資料,智慧手機會記錄使用者每一天每一秒的位置和速度資料。量化生活者會使用計步器記錄自己的心跳,運動習慣,飲食和睡眠資料。智慧汽車會蒐集行車習慣資料,智慧住宅會蒐集生活習慣資料,智慧店鋪會蒐集購物習慣資料。就連網際網路本身所代表的無所不知的巨大知識庫也是由無數相互連結的資料組成的一本百科全書;專業領域知識庫可以提供關於電影、音樂、體育賽事結果、彈珠檯機器、文化基因和雞尾酒的各種資料;以及無數由政府提供的統計資料(有一部分還是接近事實的),這些資料都充斥在你的生活中。

埋藏在這些資料下面的是對於無數問題的驚奇的答案。在這本書中,我們將會教會你如何尋找這些答案。

資料科學是什麼?

有一個關於資料科學家的笑話說,資料科學家就是那些比計算科學家知道更多統計知識而且比統計學家知道更多計算機知識的人(我個人並不認為這是一個好笑話)。事實上,從實用角度說,一些資料科學家確實是統計學家,但是另外的資料科學家則更像計算機工程師。一些資料科學家是機器學習專家,但是也存在不少資料科學家從幼兒園開始就沒有接觸過機器學習。一些資料科學家是發表過出色論文的博士,但是也有不少資料科學家是從來沒有讀過學術論文的人(對此我深表遺憾,他們應當感到羞愧)。一言以蔽之,無論人們如何定義資料科學,你一定可以找到讓這個定義完全錯誤的資料科學家。

當然這並意味著我們不能嘗試對於資料科學家下一個定義。我們認為資料科學家就是那些從混亂的原始資料中提取有用經驗和啟發性觀點的人。事實上,現實生活中也的確有不少人正在努力從資料中得到有用經驗和啟發性觀點。

比如,相親網站 OkCupid 要求使用者回答很多問題,這些問題可以幫助 OkCupid 尋找該使用者最匹配的約會物件。但是有時這些問題也可以幫助人們預測一些無傷大雅的精彩問題,比如你可以要求 OkCupid 預測你是不是能夠和你的約會物件在第一次約會的時候發生一次超友誼的親密接觸。

Facebook 也同樣要求使用者列出他們的家鄉和現居地,雖然表面上看這確實可以幫助真實生活中的朋友更加容易地聯絡到你,但是 Facebook 也利用這些資料來分析很多其他問題,比如:你的遷徙模式以及哪些地方是某支球隊的球迷來源地。

作為一家大型零售店,Target 會記錄你在線上和線下店鋪的購物互動資料。Target 會使用這些資料來預測客戶是不是懷孕了來向懷孕的使用者推銷他們的嬰幼兒用品。

2012年,奧巴馬的競選團隊僱傭了很多資料科學家。這些資料科科學家使用資料探勘技術識別出需要額外注意的選民,幫助競選團隊選擇出最佳的競選募捐策劃,同時展開更有針對性的競選募捐活動,並且用最有效的方式吸引選民參與投票。這些資料科學家們被普遍認為在奧巴馬的第二次勝選中起到了重要作用。奧巴馬的這次選舉標誌著資料科學家將成為未來選舉成功的“安全帶”,他們將使得競選活動更加依賴資料進行決策,這也將會把競選活動變成一場沒有盡頭的資料科學和資料蒐集能力的軍備競賽。

在你開始感到厭煩之前,我不得不說:有些資料科學家有時也會運用他們的才智和掌握的資料來提高政府效率,比如救助無家可歸者或者提高公眾健康水平。當然如果你志不在此,而是希望研究出最好的吸引使用者點選廣告的方法等,這些致力於提高公眾利益的資料科學家對於你的事業也完全沒有任何傷害。

假設場景 : DataSciencester

恭喜你!你已經被任命為一家專為資料科學家服務的社交網站 DataSciencester 的首席資料科學家,你將全面負責 DataSciencester 網站的資料業務。

雖然這是專為資料科學家服務的網站,但是 DataSciencester 從來沒有建立過自己的資料科學實踐(更準確地說,DataSciencester 也從來沒有建立過自己的產品)。現在該你出場了!在這本書中,你將通過解決實際工作中遇到的問題來學習資料科學的基本概念。有時我們需要研究使用者直接提供的資料,有時我們需要研究使用者和網站間接互動的資料,有時我們甚至需要研究通過我們自己設計的實驗獲得的資料。

同時因為 DataSciencester 有強烈的極客原創精神,我們需要從頭構建自己的工具。通過這本書,你會對於資料科學有一個完整的詳實理解。學完本書,在將來的工作中,我們希望你能夠運用學到的知識和技能為陷入困境的公司提供幫助或者去研究任何吸引你的問題。

歡迎加入,祝你一帆風順!(閒談一句,通常,作為資料科學家你可以在週五穿著牛仔褲上班而且樓下的浴室隨時待命。)

尋找關鍵聯絡人

好了,現在你開始了在 DataSciencester 工作的第一天,公司負責客戶網路的高管對於 DataSciencester 網站的使用者有不少疑問,以前他找不到人幫忙,現在他非常高興你加入了公司並且希望你可以幫助到他。

首先,他特別希望你幫助他在資料科學家使用者中找出那些“關鍵聯絡人”。所以他給了你他已經得到的關於整個DataSciencester 使用者關係的資料(但是,你需要注意的是,在真實的工作中,你常常得不到到你希望的資料而不得不自己動手獲取資料,我們將在第 9 章詳細討論如何獲取需要的資料)。

這些資料到底是什麼呢?它是一個包括所有使用者的 Python 列表,列表的每一個元素都是一個字典,字典包含了使用者的 id 數字和使用者的姓名(巧合的是,這些 id 的使用者名稱都有和 id 數字諧音的部分)

users = [{ "id": 0, "name": "Hero" },{ "id": 1, "name": "Dunn" },{ "id": 2, "name": "Sue" },{ "id": 3, "name": "Chi" },{ "id": 4, "name": "Thor" },{ "id": 5, "name": "Clive" },{ "id": 6, "name": "Hicks" },{ "id": 7, "name": "Devin" },{ "id": 8, "name": "Kate" },{ "id": 9, "name": "Klein" }]

同時你得到了一組表示“友誼關係”的資料,就是如下的這個包含 id 號碼的friendships列表:

friendships = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (3, 4), (4, 5), (5, 6), (5, 7), (6, 8), (7, 8), (8, 9)]

比如,元組 (0, 1) 代表 id 為 0 的資料科學家( Hero ) 和 id 為 1 的資料科學家 ( Dunn ) 是朋友。這個關係也可以用圖 1-1 來表示:

暫時不要在程式碼的細節上糾纏太久。在第 2 章中,我們會帶著你快速的學習 Python 。現在你只需要大致理解這些程式碼是為了實現哪些目標即可。

因為我們使用 Python 的字典結構來表示使用者,所以我們可以非常方便地新增更多的資料。

比如,我們可以嘗試給每一個使用者新增一個朋友列表。首先我們對每一個使用者建立一個代表朋友屬性的空列表。

for user in users:
    user["friends"] = []

然後我們可以通過friendships資料來填充friends屬性列表。

for i, j in friendships:
#這段程式碼可以工作是因為 users[i] 就是 id 為 i 的使用者
    users[i]["friends"].append(users[j])                   
# 給使用者 i 新增朋友 j
    users[j]["friends"].append(users[i]) 
# 給使用者 j 新增朋友 i

一旦每一個使用者字典都包括了一個朋友列表,我們就可以很容易地進一步探索朋友關係圖的性質,比如 “平均一個使用者有多少個朋友?”。

要回答這個問題,首先我們必須找出所有的朋友關係數,這隻需要統計朋友列表的長度就可以了。

def number_of_friends(user):
    """每一個使用者有多少朋友"""
    return len(user["friends"])                             # 朋友列表長度
total_connections = sum(number_of_friends(user) for user in users)                   
# 24

這樣我們只需要簡單地除以使用者數即可得到平均一個使用者有多少朋友了:

from __future__ import division                
#引入整數除法特性#注意該語句必須是模組或程式的第一個語句。
num_users = len(users)
#列表長度為10
avg_connections = total_connections / num_users
#每一個使用者平均擁有的朋友數 2.4

同樣的思路我們也可以很容易地找出朋友關係最多的人——他們就是有最多朋友數目的人。

因為資料量不是特別大,所以我們可以很容易地對所有的使用者按照從“朋友最多的人”到“朋友最少的人”的順序進行排序:

#建立一個朋友數目列表num_friends_by_id
num_friends_by_id = [(user["id"],number_of_friends(user)) for user in users] 
sorted(num_friends_by_id, #排序列表
   key=lambda (users_id , num_friends): num_friends, 
   #依照num_friends排序
   reverse=True)                                     
   #倒序輸出,從最大到最小#num_friends_by_id輸出結果如下,每一對都是(user_id,num_friends)組合:#[(0, 2), (1, 3), (2, 3), (3, 3), (4, 2),# (5, 3),(6, 2), (7, 2), (8, 3), (9, 1)]

如果換一個思路,從社交網路的角度來理解我們剛剛完成的工作,就是找出這個使用者關係網路中佔據最中心位置的人。事實上,我們剛剛計算了這個網路的重要屬性之一:程度中心性(見圖 1-2).

程度中心性非常方便計算, 但是不能給出更多準確的細節資訊。比如,在 DateSciencester 的使用者朋友網路中我們知道,使用者 Thor (id 為 4 )只有 2 個朋友關係,而使用者 Dunn ( id 為 1 )有 3 個朋友關係。但是回頭看一看上面展示的網路圖,似乎 Thor 更加具有程度中心性。 在第 21 章,我們將會更加仔細地討論網路的性質和研究更加複雜的中心性定義,這些更加複雜的中心性可能會更加合適。

你可能認識的資料科學家

當你正在努力填寫新員工登記表的時候,負責人事的高管來到你的辦公桌前。她希望能夠激發資料科學家之間更多的交流和聯絡,因此她希望你能夠策劃一個“你可能認識的資料科學家”的提示功能。

你的直覺告訴你一個使用者很有可能認識自己朋友的朋友。這個想法非常容易驗證:對於每一個使用者的朋友們,驗證這個朋友的朋友是不是被這個使用者認識,最後合併結果即可檢測這個想法是不是可靠:

def friends_of_friend_ids_bad(user):
# "foaf" 是 "friend of a friend"的簡稱
    return [foaf["id"] for friend in user["friends"] 
    # 對於每一個使用者的朋友們
    for foaf in friend["friends"]] 
    # 檢驗這個朋友的朋友是不是這個使用者的朋友

當我們把上面的函式作用在第一個使用者users[0]上的時候,friends_of_friend_ids_bad(users[0])給出如下的結果

[0, 2, 3, 0, 1, 3]

結果中包括了使用者 0 兩次,因為使用者 0 ( Hero )確實同時是他的兩個朋友的朋友。結果中也包括使用者 1 和使用者 2 ,雖然他們已經是使用者 1 的朋友。同時他也包括了使用者 3 兩次,因為使用者 3 ( Chi ) 可以通過使用者 0 的兩個朋友和使用者 0 聯絡起來,具體的驗證程式碼如下:

print [friend["id"] for friend in users[0]["friends"]] #[1, 2]print [friend["id"] for friend in users[1]["friends"]] #[0, 2, 3]print [friend["id"] for friend in users[2]["friends"]] #[0, 1, 3]

知道人們可以藉助自己朋友的朋友互相認識彼此是非常有趣的資訊,所以或許我們應該統計下通過共同朋友可能成為朋友的數目。為了實現這個目的,我們需要藉助輔助函式來排除已經彼此認識成為朋友的那批使用者:

from collections import Counter
# 並不預設載入collection函式def not_the_same(user, other_user):"""排除相同使用者"""
    return user["id"] != other_user["id"]

def not_friends(user, other_user):"""other_user使用者並不是user使用者的朋友;也就是 other_user並不和user使用者的friends列表中個的使用者相同"""
     return all(not_the_same(friend, other_user)
           for friend in user["friends"])

def friends_of_friend_ids(user):
    return Counter(foaf["id"]
               for friend in user["friends"]  # 對於每一個user使用者的朋友
               for foaf in friend["friends"]  # 對於每一個user使用者朋友的朋友
               if not_the_same(user, foaf)    # 排除相同使用者
               and not_friends(user, foaf))   # 排除已經是朋友的使用者

print friends_of_friend_ids(users[3]) # Counter({0: 2, 5: 1})

這個輸出結果正確地說明使用者 Chi (id 為 3 ) 和使用者 Hero ( id 為 0 ) 之間有 2 個共同朋友,而和使用者 Clive ( id 為 5) 只有 1 個共同使用者。

作為一個數據科學家,你知道大家都喜歡遇到和自己有共同興趣的人。(事實上,下面要做的這個小探索是對資料科學家需要掌握的專業技能的精彩展示。) 通過諮詢朋友,你得到了如下的資料,這個列表的每一個元素都包括一個由使用者 id 和興趣 interest 組成的元組 。

interests = [(0, "Hadoop"), (0, "Big Data"), (0, "HBase"), (0, "Java"),(0, "Spark"), (0, "Storm"), (0, "Cassandra"),(1, "NoSQL"), (1, "MongoDB"), (1, "Cassandra"), (1, "HBase"),(1, "Postgres"), (2, "Python"), (2, "scikit-learn"), (2, "scipy"),(2, "numpy"), (2, "statsmodels"), (2, "pandas"), (3, "R"), (3, "Python"),(3, "statistics"), (3, "regression"), (3, "probability"),(4, "machine learning"), (4, "regression"), (4, "decision trees"),(4, "libsvm"), (5, "Python"), (5, "R"), (5, "Java"), (5, "C++"),(5, "Haskell"), (5, "programming languages"), (6, "statistics"),(6, "probability"), (6, "mathematics"), (6, "theory"),(7, "machine learning"), (7, "scikit-learn"), (7, "Mahout"),(7, "neural networks"), (8, "neural networks"), (8, "deep learning"),(8, "Big Data"), (8, "artificial intelligence"), (9, "Hadoop"),(9, "Java"), (9, "MapReduce"), (9, "Big Data")]

比如,使用者 Thor ( id 為 4 ) 和使用者 Devin ( id 為 7 ) 沒有任何相同的朋友,但是他們都對於機器學習有興趣。

非常容易地我們就可以構建一個函式尋找有相同興趣的使用者:

def data_scientists_who_like(target_interest):
    return [user_id 
        for user_id, user_interest in interests 
        if user_interest == target_interest]

雖然上面的方法可以正確得出我們期望的結果,但是每一次都必須遍歷整個興趣列表。如果我們有很多的使用者和興趣對或者我們希望做大量的查詢,這樣的程式效率就比較低來。因此,我們應該專門建立一個從興趣到使用者的檢索:

from collections import defaultdict
# 字典的鍵是興趣,值是對該興趣感興趣使用者名稱列表
user_ids_by_interest = defaultdict(list)for user_id, interest in interests:
    user_ids_by_interest[interest].append(user_id)

另一種形式是從使用者到興趣的檢索:

# 鍵是使用者名稱,值是該使用者的興趣列表interests_by_user_id = defaultdict(list)for user_id, interest in interests:
   interests_by_user_id[user_id].append(interest)

現在我們可以很容易的找到對於一個特定的使用者和他有最多相同興趣的使用者了,具體思路如下:

  • 對該使用者的興趣列表做一次迴圈
  • 對於該使用者的每一個興趣,在相應的興趣對應的使用者列表中再次迴圈
  • 記錄下每一個使用者在這樣的迴圈中出現的次數

具體實現的程式碼為:

 def most_common_interests_with(user_id):
     return Counter(interested_user_id 
        for interest in interests_by_user_id[user_id]   
         for interested_user_id in user_ids_by_interest[interest]
         if interested_user_id != user_id)

或許將來,我們可以通過這個方法整合共同朋友和共同興趣資料來構建一個更加豐富的“你應該知道的資料科學家”的功能。在第 22 章,我們將深入討論這一點。

工資和經驗的關係

現在你打算去吃午飯,但是負責公共關係的高管詢問你是不是能夠提供一些關於資料科學家收入的有趣事實。收入資料當然是非常敏感的資料,所以負責公共關係的高管給你提供的是匿名後的收入資料(單位:美元)和工作年限資料(單位:年)。

salaries_and_tenures = [(83000, 8.7), (88000, 8.1), 
                        (48000, 0.7), (76000, 6),
                        (69000, 6.5), (76000, 7.5),
                        (60000, 2.5), (83000, 10),
                        (48000, 1.9), (63000, 4.2)]

非常自然地,第一步就是先對這些資料先做一副關係圖來探索可能存在的關係(我們將在第 3 章研究如何畫圖)。現在你可以在圖 1-3 看到畫圖的結果:

趨勢看起來非常明顯,工作時間越長掙得越多。但是你怎麼把這幅圖轉化成一個有趣的故事?你的第一個想法就是檢視不同工作年限的平均工資:

# 鍵是工作年限,值是每一個工作年限的工資salary_by_tenure = defaultdict(list)for salary, tenure in salaries_and_tenures:
    salary_by_tenure[tenure].append(salary)# 鍵是工作年限,值是每一個工作年限的平均工資average_salary_by_tenure = { 
    tenure : sum(salaries) / len(salaries)   
    for tenure, salaries in salary_by_tenure.items() }

當然,這看起來不是特別有用,因為沒有一個使用者有相同的工作年限,這也就意味著我們其實只是在報告每一個單獨使用者的工資而不是多個使用者的平均工資,具體結果如下:

{0.7: 48000,
 1.9: 48000,
 2.5: 60000,
 4.2: 63000,
   6: 76000,
 6.5: 69000,
 7.5: 76000,
 8.1: 88000,
 8.7: 83000,
  10: 83000}

更有用的可能是將工作年限做一個粗略地分組再進行統計,程式碼如下:

def tenure_bucket(tenure):
    if tenure < 2: 
        return "less than two"
    elif tenure < 5: 
        return "between two and five"
    else: 
        return "more than five"    

然後把屬於同一個工作年限分組的工資資料合併到一個列表中,具體程式碼如下:

#鍵是工作年限分組資料,值是該工作年限分組對應的工資列表salary_by_tenure_bucket = defaultdict(list)for salary, tenure in salaries_and_tenures:
     bucket = tenure_bucket(tenure)
     salary_by_tenure_bucket[bucket].append(salary)

最後對每一個工作年限分組計算平均值,具體程式碼如下:

average_salary_by_bucket = {
    tenure_bucket : sum(salaries) / len(salaries)
    for tenure_bucket, salaries in salary_by_tenure_bucket.iteritems()
 }

這樣我們可以得到一個更加有意思的結果:

{'between two and five': 61500,
  'less than two': 48000,
  'more than five': 79166}

現在終於你有了一個可以大聲宣傳的有趣的事實:“有5年以上工作經驗的資料科學家比菜鳥資料科學家可以多掙65%”。

當然,我們必須承認我們的分組標準是粗略選擇的。事實上我們真正想說明的是,平均而言,更多的工作經驗對於工資的會有積極的影響。更進一步,為了做出一些更加吸引人的有趣事實,或許我們應該對於未來做一些大膽的預測。雖然我們可能並不知道的這些工作年限對應的工資資料。我們將在第 14 章仔細研究這個思路。

付費使用者

當你吃完午飯回到辦公室的時候,負責財務的高管正在等你。她希望能夠更好地區分出付費使用者和未付費使用者。(她已經知道付費使用者和未付費使用者的使用者名稱,但是沒有更進一步的資訊。)

你注意到工作年限和是否付費之間似乎存在某種聯絡。

0.7    paid
1.9    unpaid    
2.5    paid    
4.2    unpaid    
6      unpaid
6.5    unpaid
7.5    unpaid
8.1    unpaid
8.7    paid    
10     paid

工作年限較長和較短的使用者傾向於付費,但是接近平均工作年限的使用者常常不傾向於付費。

因此,你打算建立一個分類模型來區分付費使用者和未付費使用者。當然需要說明的是,雖然目前的資料量確實不足以建立一個可靠的模型,但是我們可以只是進行初步的嘗試。所以你嘗試認為工作年限較長和較短的使用者是付費使用者,但是接近平均工作年限的使用者是未付費使用者。具體的分類模型程式碼如下:

def predict_paid_or_unpaid(years_experience):
    if years_experience < 3.0:
        return "paid"
    elif years_experience < 8.5:
        return "unpaid"
    else:
        return "paid"

當然,我們只是粗略地估測了分段點的位置。

隨著資料量和數學知識的增加,我們可以建立一個更加可靠的基於使用者工作年限來預測使用者付費可能性的模型。我們會在第 16 章仔細研究這個問題。

熱門話題

當你在 DataSciencester 第一天的工作接近尾聲準備下班的時候,負責產品內容管理的高管向你諮詢使用者們最感興趣的熱門話題是哪些,因為她需要安排公司部落格釋出內容的日程,通常這些部落格的內容就是使用者門最感興趣的話題。你已經從前面“你可能認識的資料科學家”專案中“推薦擁有共同興趣的陌生使用者”部分得到了如下的使用者興趣資料:

interests = [(0, "Hadoop"), (0, "Big Data"), (0, "HBase"), (0, "Java"),(0, "Spark"), (0, "Storm"), (0, "Cassandra"),(1, "NoSQL"), (1, "MongoDB"), (1, "Cassandra"), (1, "HBase"),(1, "Postgres"), (2, "Python"), (2, "scikit-learn"), (2, "scipy"),(2, "numpy"), (2, "statsmodels"), (2, "pandas"), (3, "R"), (3, "Python"),(3, "statistics"), (3, "regression"), (3, "probability"),(4, "machine learning"), (4, "regression"), (4, "decision trees"),(4, "libsvm"), (5, "Python"), (5, "R"), (5, "Java"), (5, "C++"),(5, "Haskell"), (5, "programming languages"), (6, "statistics"),(6, "probability"), (6, "mathematics"), (6, "theory"),(7, "machine learning"), (7, "scikit-learn"), (7, "Mahout"),(7, "neural networks"), (8, "neural networks"), (8, "deep learning"),(8, "Big Data"), (8, "artificial intelligence"), (9, "Hadoop"),(9, "Java"), (9, "MapReduce"), (9, "Big Data")]

一個簡單但是可能不是那麼激動人心的方法就是僅僅從關鍵詞被提及的頻數角度來找出最受歡迎的興趣,具體步驟如下:

  1. 把興趣列表中的單詞轉化為小寫(因為不同的使用者可能大寫也可能不大寫同一個關鍵詞的開頭字母)
  2. 對興趣片語進行分詞
  3. 統計分詞結果中,每一個不同單詞的出現次數

具體程式碼如下:

words_and_counts = Counter(word 
    for user, interest in interests
        for word in interest.lower().split())

這樣我們就可以很方便的輸出所有出現次數大於 1 的單詞,具體程式碼如下:

for word, count in words_and_counts.most_common():
    if count > 1:
        print word, count

這樣就可以得到你期望的結果了(除非你希望 “scikit-learn” 應該被分割開來,這時候確實沒有給出你期望的結果),具體結果如下:

learning 3
java 3
python 3
big 3
data 3
hbase 2
regression 2
cassandra 2
statistics 2
probability 2
hadoop 2
networks 2
machine 2
neural 2
scikit-learn 2
r 2

我們會在第 20 章探討更加高階的方法來提取熱門話題。

原文連結:http://www.ituring.com.cn/article/199625