利用python进行数据分析第二版学习笔记
行話:
數據規整(Munge/Munging/Wrangling) 指的是將非結構化和(或)散亂數據處理為結構化或整潔形式的整個過程。這幾個詞已經悄悄成為當今數據黑客們的行話了。Munge這個詞跟Lunge押韻。
偽碼(Pseudocode) 算法或過程的“代碼式”描述,而這些代碼本身并不是實際有效的源代碼。
語法糖(Syntactic sugar) 這是一種編程語法,它并不會帶來新的特性,但卻能使代碼更易讀、更易寫。
Python的對象通常都有屬性(其它存儲在對象內部的Python對象)和方法(對象的附屬函數可以訪問對象的內部數據)。可以用 obj.attribute_name 訪問屬性和方法:
你可以用continue使for循環提前,跳過剩下的部分。看下面這個例子,將一個列表中的整數相加,跳過None:
sequence = [1, 2, None, 4, None, 5] total = 0 for value in sequence: if value is None: continue total += value可以用 break 跳出for循環。下面的代碼將各元素相加,直到遇到5:
sequence = [1, 2, 0, 4, 6, 5, 2, 1] total_until_5 = 0 for value in sequence: if value == 5: break total_until_5 += valuebreak只中斷for循環的最內層,其余的for循環仍會運行:
While循環
while循環指定了條件和代碼,當條件為False或用break退出循環,代碼才會退出:
三元表達式
Python中的三元表達式可以將if-else語句放到一行里。語法如下:
value = true-expr if condition else false-expr和if-else一樣,只有一個表達式會被執行。因此,三元表達式中的if和else可以包含大量的計算,但只有True的分支會被執行。因此,三元表達式中的if和else可以包含大量的計算,但只有True的分支會被執行。
雖然使用三元表達式可以壓縮代碼,但會降低代碼可讀性。
第 3 章 Python 的數據結構、函數和文件
數據的結構和序列
元組,列表,字典,集合
用tuple可以將任意序列或迭代器轉換成元組:
拆分元組
如果你想將元組賦值給類似元組的變量,Python會試圖拆分等號右邊的值:
使用這個功能,你可以很容易地替換變量的名字,其它語言可能是這樣:
變量拆分常用來迭代元組或列表序列:
In [10]: seq = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]In [11]: for a, b, c in seq:...: print('a={0}, b={1}, c={2}'.format(a, b, c))...: a=1, b=2, c=3 a=4, b=5, c=6 a=7, b=8, c=9另一個常見用法是從函數返回多個值。后面會詳解。
Python最近新增了更多高級的元組拆分功能,允許從元組的開頭“摘取”幾個元素。它使用了特殊的語法 *rest ,這也用在函數簽名中以抓取任意長度列表的位置參數:
rest 的部分是想要舍棄的部分,rest的名字不重要。作為慣用寫法,許多Python
程序員會將不需要的變量使用下劃線:
tuple方法:
因為元組的大小和內容不能修改,它的實例方法都很輕量。其中一個很有用的就是 count (也適用于列表),它可以統計某個值得出現頻率:
列表
可以用append在列表末尾添加元素
In [45]: b_list.append('dwarf') In [46]: b_list Out[46]: ['foo', 'peekaboo', 'baz', 'dwarf']在列表中檢查是否存在某個值遠比字典和集合速度慢,因為Python是線性搜索列表中的值,但在字典和集合中,在同樣的時間內還可以檢查其它項(基于哈希表)。
排序
你可以用 sort 函數將一個列表原地排序(不創建新的對象):
In [61]: a = [7, 2, 5, 1, 3] In [62]: a.sort() In [63]: a Out[63]: [1, 2, 3, 5, 7]另還有二級排序key
一個聰明的方法是使用 -1 ,它可以將列表或元組顛倒過來:
In [82]: seq[::-1] Out[82]: [1, 0, 6, 5, 3, 6, 3, 2, 7]序列函數
Python有一些有用的序列函數。
enumerate函數
迭代一個序列時,你可能想跟蹤當前項的序號
sorted函數
sorted 函數可以從任意序列的元素返回一個新的排好序的列表:
zip函數
zip 可以將多個列表、元組或其它序列成對組合成一個元組列表:
zip 可以處理任意多的序列,元素的個數取決于最短的序列:
In [93]: seq3 = [False, True] In [94]: list(zip(seq1, seq2, seq3)) Out[94]: [('foo', 'one', False), ('bar', 'two', True)]zip 的常見用法之一是同時迭代多個序列,可能結合 enumerate 使用:
In [95]: for i, (a, b) in enumerate(zip(seq1, seq2)): ....: print('{0}: {1}, {2}'.format(i, a, b)) ....: 0: foo, one 1: bar, two 2: baz, three給出一個“被壓縮的”序列, zip 可以被用來解壓序列。也可以當作把行的列表轉換
為列的列表。這個方法看起來有點神奇:
reversed函數
reversed 可以從后向前迭代一個序列:
要記住 reversed 是一個生成器(后面詳細介紹),只有實體化(即列表或for循環)之后才能創建翻轉的序列。
字典
字典可能是Python最為重要的數據結構。它更為常見的名字是哈希映射或關聯數組。它是鍵值對的大小可變集合,鍵和值都是Python對象。創建字典的方法之一是使用尖括號,用冒號分隔鍵和值:
默認值
下面的邏輯很常見:
因此,dict的方法get和pop可以取默認值進行返回,上面的if-else語句可以簡寫成下面
value = some_dict.get(key, default_value)get默認會返回None,如果不存在鍵,pop會拋出一個例外。關于設定值,常見的情況是在字典的值是屬于其它集合,如列表。例如,你可以通過首字母,將一個列表中的單詞分類:
In [123]: words = ['apple', 'bat', 'bar', 'atom', 'book'] In [124]: by_letter = {} In [125]: for word in words: .....: letter = word[0] .....: if letter not in by_letter: .....: by_letter[letter] = [word] .....: else: .....: by_letter[letter].append(word) .....: In [126]: by_letter Out[126]: {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}setdefault 方法就正是干這個的。前面的for循環可以改寫為:
for word in words: letter = word[0] by_letter.setdefault(letter, []).append(word)collections 模塊有一個很有用的類, defaultdict ,它可以進一步簡化上面。傳遞類型或函數以生成每個位置的默認值:
from collections import defaultdict by_letter = defaultdict(list) for word in words: by_letter[word[0]].append(word)有效的鍵類型
字典的值可以是任意Python對象,而鍵通常是不可變的標量類型(整數、浮點型、字符串)或元組(元組中的對象必須是不可變的)。這被稱為“可哈希性”。可以用 hash 函數檢測一個對象是否是可哈希的(可被用作字典的鍵):
要用列表當做鍵,一種方法是將列表轉化為元組,只要內部元素可以被哈希,它也
就可以被哈希:
列表、集合和字典推導式
列表推導式是Python最受喜愛的特性之一。它允許用戶方便的從一個集合過濾元素,形成列表,在傳遞參數的過程中還可以修改元素。形式如下:
用相似的方法,還可以推導集合和字典。字典的推導式如下所示:
dict_comp = {key-expr : value-expr for value in collection if condition}集合的推導式與列表很像,只不過用的是尖括號:
set_comp = {expr for value in collection if condition}與列表推導式類似,集合與字典的推導也很方便,而且使代碼的讀寫都很容易。來看前面的字符串列表。假如我們只想要字符串的長度,用集合推導式的方法非常方便:
In [156]: unique_lengths = {len(x) for x in strings} In [157]: unique_lengths Out[157]: {1, 2, 3, 4, 6}map 函數可以進一步簡化:
In [158]: set(map(len, strings)) Out[158]: {1, 2, 3, 4, 6}函數
def my_function(x, y, z=1.5): # x,y:位置參數,z:關鍵字參數(關鍵字參數通常用于指定默認值或可選參數)if z > 1:return z * (x + y)else:return z / (x + y)生成器
能以一種一致的方式對序列進行迭代(比如列表中的對象或文件中的行)是Python的一個重要特點。這是通過一種叫做迭代器協議(iterator protocol,它是一種使對象可迭代的通用方式)的方式實現的,一個原生的使對象可迭代的方法。比如說,對字典進行迭代可以得到其所有的鍵:
In [180]: some_dict = {'a': 1, 'b': 2, 'c': 3} In [181]: for key in some_dict: .....: print(key) a b c迭代器是一種特殊對象,它可以在諸如for循環之類的上下文中向Python解釋器輸送對象。大部分能接受列表之類的對象的方法也都可以接受任何可迭代對象。比如min、max、sum等內置方法以及list、tuple等類型構造器:
In [182]: dict_iterator = iter(some_dict) In [183]: dict_iterator Out[183]: <dict_keyiterator at 0x7fbbd5a9f908> In [184]: list(dict_iterator) Out[184]: ['a', 'b', 'c']生成器(generator)是構造新的可迭代對象的一種簡單方式。一般的函數執行之后只會返回單個值,而生成器則是以延遲的方式返回一個值序列,即每返回一個值之后暫停,直到下一個值被請求時再繼續。要創建一個生成器,只需將函數中的return替換為yeild即可:
def squares(n=10): print('Generating squares from 1 to {0}'.format(n ** 2)) for i in range(1, n + 1): yield i ** 2調用該生成器時,沒有任何代碼會被立即執行:
In [186]: gen = squares() In [187]: gen Out[187]: <generator object squares at 0x7fbbd5ab4570>直到你從該生成器中請求元素時,它才會開始執行其代碼:
In [188]: for x in gen: .....: print(x, end=' ') Generating squares from 1 to 100 1 4 9 16 25 36 49 64 81 100生成器表達式
另一種更簡潔的構造生成器的方法是使用生成器表達式(generator expression)。這是一種類似于列表、字典、集合推導式的生成器。其創建方式為,把列表推導式兩端的方括號改成圓括號:
In [189]: gen = (x ** 2 for x in range(100)) In [190]: gen Out[190]: <generator object <genexpr> at 0x7fbbd5ab29e8>它跟下面這個冗長得多的生成器是完全等價的:
def _make_gen(): for x in range(100): yield x ** 2 gen = _make_gen()生成器表達式也可以取代列表推導式,作為函數參數:
In [191]: sum(x ** 2 for x in range(100)) Out[191]: 328350 In [192]: dict((i, i **2) for i in range(5)) Out[192]: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}itertools模塊
標準庫itertools模塊中有一組用于許多常見數據算法的生成器。例如,groupby可以接受任何序列和一個函數。它根據函數的返回值對序列中的連續元素進行分組。下面是一個例子:
In [193]: import itertools In [194]: first_letter = lambda x: x[0] In [195]: names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Ste ven'] In [196]: for letter, names in itertools.groupby(names, first_le tter): .....: print(letter, list(names)) # names is a generator A ['Alan', 'Adam'] W ['Wes', 'Will'] A ['Albert'] S ['Steven']錯誤和異常處理
f = open(path, 'w') try: write_to_file(f) except: print('Failed') else: print('Succeeded') finally: f.close()文件和操作系統
默認情況下,文件是以只讀模式(‘r’)打開的
In [207]: path = 'examples/segismundo.txt' In [208]: f = open(path) In [209]: lines = [x.rstrip() for x in open(path)] In [211]: f.close()用with語句可以可以更容易地清理打開的文件:
In [212]: with open(path) as f: .....: lines = [x.rstrip() for x in f]這樣可以在退出代碼塊時,自動關閉文件。
如果輸入f =open(path,‘w’),就會有一個新文件被創建在examples/segismundo.txt,并覆蓋掉該位置原來的任何數據。另外有一個x文件模
式,它可以創建可寫的文件,但是如果文件路徑存在,就無法創建。表3-3列出了所有的讀/寫模式。
Numpy基礎:數組和矢量計算
對于大部分數據分析應用而言,我最關注的功能主要集中在:
1)用于數據整理和清理、子集構造和過濾、轉換等快速的矢量化數組運算;
2)常用的數組算法,如排序、唯一化、集合運算等;
3)高效的描述統計和數據聚合/摘要運算;
4)用于異構數據集的合并/連接運算的數據對齊和關系型數據運算;
5)將條件邏輯表述為數組表達式(而不是帶有if-elif-else分支的循環);
6)數據的分組運算(聚合、轉換、函數應用等)。
NumPy之于數值計算特別重要的原因之一,是因為它可以高效處理大數組的數據。這是因為:
1)NumPy是在一個連續的內存塊中存儲數據,獨立于其他Python內置對象。NumPy的C語言編寫的算法庫可以操作內存,而不必進行類型檢查或其它前期工作。比起Python的內置序列,NumPy數組使用的內存少;
2)NumPy可以在整個數組上執行復雜的計算,而不需要Python的for循環。
要搞明白具體的性能差距,考察一個包含一百萬整數的數組,和一個等價的Python列表:
In [7]: import numpy as np In [8]: my_arr = np.arange(1000000) In [9]: my_list = list(range(1000000))各個序列分別乘以2:
In [10]: %time for _ in range(10): my_arr2 = my_arr * 2 CPU times: user 20 ms, sys: 50 ms, total: 70 ms Wall time: 72.4 ms In [11]: %time for _ in range(10): my_list2 = [x * 2 for x in my _list] CPU times: user 760 ms, sys: 290 ms, total: 1.05 s Wall time: 1.05 s基于NumPy的算法要比純Python快10到100倍(甚至更快),并且使用的內存更少。
NumPy最重要的一個特點就是其N維數組對象(即ndarray),該對象是一個快速而靈活的大數據集容器。你可以利用這種數組對整塊數據執行一些數學運算,其語法跟標量元素之間的運算一樣。
筆記:當你在本書中看到“數組”、“NumPy數組”、"ndarray"時,基本上都指的是同一樣東西,即ndarray對象
ndarray是一個通用的同構數據多維容器,也就是說,其中的所有元素必須是相同類型的。每個數組都有一個shape(一個表示各維度大小的元組)和一個dtype(一個用于說明數組數據類型的對象):
In [17]: data.shape Out[17]: (2, 3) In [18]: data.dtype Out[18]: dtype('float64')創建數組最簡單的辦法就是使用array函數。它接受一切序列型的對象(包括其他數組),然后產生一個新的含有傳入數據的NumPy數組。以一個列表的轉換為例:
In [19]: data1 = [6, 7.5, 8, 0, 1] In [20]: arr1 = np.array(data1) In [21]: arr1 Out[21]: array([ 6. , 7.5, 8. , 0. , 1. ])arange是Python內置函數range的數組版
In [32]: np.arange(15) Out[32]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])表4-1列出了一些數組創建函數。由于NumPy關注的是數值計算,因此,如果沒有特別指定,數據類型基本都是float64(浮點數)。
NumPy數組的運算
數組很重要,因為它使你不用編寫循環即可對數據執行批量運算。NumPy用戶稱其為矢量化(vectorization)。大小相等的數組之間的任何算術運算都會將運算應用到元素級:
不同大小的數組之間的運算叫做廣播(broadcasting)
如上所示,當你將一個標量值賦值給一個切片時(如arr[5:8]=12),該值會自動傳播(也就說后面將會講到的“廣播”)到整個選區。跟列表最重要的區別在于,數組切片是原始數組的視圖。這意味著數據不會被復制,視圖上的任何修改都會直接反映到源數組上。
如果你剛開始接觸NumPy,可能會對此感到驚訝(尤其是當你曾經用過其他熱衷于復制數組數據的編程語言)。由于NumPy的設計目的處理大數據,所以你可以想象一下,假如NumPy堅持要將數據復制來復制去的話會產生何等的性能和內存問題。
注意:如果你想要得到的是ndarray切片的一份副本而非視圖,就需要明確地進行復制操作,例如 arr[5:8].copy()
圖4-1說明了二維數組的索引方式。軸0作為行,軸1作為列。
利用數組進行數據處理
NumPy數組使你可以將許多種數據處理任務表述為簡潔的數組表達式(否則需要編寫循環)。用數組表達式代替循環的做法,通常被稱為矢量化。一般來說,矢量化數組運算要比等價的純Python方式快上一兩個數量級(甚至更多),尤其是各種數值計算。在后面內容中(見附錄A)我將介紹廣播,這是一種針對矢量化計算的強大手段。
Pandas入門
要使用pandas,你首先就得熟悉它的兩個主要數據結構:Series和DataFrame。雖然它們并不能解決所有問題,但它們為大多數應用提供了一種可靠的、易于使用的基礎。
Series
Series是一種類似于一維數組的對象,它由一組數據(各種NumPy數據類型)以及一組與之相關的數據標簽(即索引)組成。僅由一組數據即可產生最簡單的Series:
In [11]: obj = pd.Series([4, 7, -5, 3]) In [12]: obj Out[12]: 0 4 1 7 2 -5 3 3 dtype: int64Series的字符串表現形式為:索引在左邊,值在右邊。由于我們沒有為數據指定索引,于是會自動創建一個0到N-1(N為數據的長度)的整數型索引。你可以通過Series 的values和index屬性獲取其數組表示形式和索引對象
通常,我們希望所創建的Series帶有一個可以對各個數據點進行標記的索引:
In [15]: obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', ' c']) In [16]: obj2 Out[16]: d 4 b 7 a -5 c 3 dtype: int64 In [17]: obj2.index Out[17]: Index(['d', 'b', 'a', 'c'], dtype='object')還可以將Series看成是一個定長的有序字典,因為它是索引值到數據值的一個映射。它可以用在許多原本需要字典參數的函數中:
In [24]: 'b' in obj2 Out[24]: True In [25]: 'e' in obj2 Out[25]: False對于許多應用而言,Series最重要的一個功能是,它會根據運算的索引標簽自動對齊數據:
DataFrame
DataFrame是一個表格型的數據結構,它含有一組有序的列,每列可以是不同的值類型(數值、字符串、布爾值等)。DataFrame既有行索引也有列索引,它可以被看做由Series組成的字典(共用同一個索引)。DataFrame中的數據是以一個或多個二維塊存放的(而不是列表、字典或別的一維數據結構)。有關DataFrame內部的技術細節遠遠超出了本書所討論的范圍
建DataFrame的辦法有很多,最常用的一種是直接傳入一個由等長列表或NumPy數組組成的字典:
通過類似字典標記的方式或屬性的方式,可以將DataFrame的列獲取為一個Series:
基本功能
重新索引
pandas對象的一個重要方法是reindex,其作用是創建一個新對象,它的數據符合新的索引
借助DataFrame,reindex可以修改(行)索引和列。只傳遞一個序列時,會重新索引結果的行:
In [98]: frame = pd.DataFrame(np.arange(9).reshape((3, 3)), ....: index=['a', 'c', 'd'], ....: columns=['Ohio', 'Texas', 'Califor nia']) In [99]: frame Out[99]: Ohio Texas California a 0 1 2 c 3 4 5 d 6 7 8 In [100]: frame2 = frame.reindex(['a', 'b', 'c', 'd']) In [101]: frame2 Out[101]: Ohio Texas California a 0.0 1.0 2.0 b NaN NaN NaN c 3.0 4.0 5.0 d 6.0 7.0 8.0列可以用columns關鍵字重新索引:
In [102]: states = ['Texas', 'Utah', 'California'] In [103]: frame.reindex(columns=states) Out[103]: Texas Utah California a 1 NaN 2 c 4 NaN 5 d 7 NaN 8丟棄指定軸上的項
丟棄某條軸上的一個或多個項很簡單,只要有一個索引數組或列表即可。由于需要執行一些數據整理和集合邏輯,所以drop方法返回的是一個在指定軸上刪除了指定值的新對象:
對于DataFrame,可以刪除任意軸上的索引值。為了演示,先新建一個DataFrame例子:
In [110]: data = pd.DataFrame(np.arange(16).reshape((4, 4)), .....: index=['Ohio', 'Colorado', 'Utah', 'New York'], .....: columns=['one', 'two', 'three', 'f our']) In [111]: data Out[111]: one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 Utah 8 9 10 11 New York 12 13 14 15用標簽序列調用drop會從行標簽(axis 0)刪除值:
通過傳遞axis=1或axis='columns’可以刪除列的值:
許多函數,如drop,會修改Series或DataFrame的大小或形狀,可以就地修改對象,不會返回新的對象:
In [115]: obj.drop('c', inplace=True) In [116]: obj Out[116]: a 0.0 b 1.0 d 3.0 e 4.0 dtype: float64小心使用inplace,它會銷毀所有被刪除的數據。
索引、選取和過濾
Series索引(obj[…])的工作方式類似于NumPy數組的索引,只不過Series的索引值不只是整數。下面是幾個例子:
In [117]: obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd']) In [118]: obj Out[118]: a 0.0 b 1.0 c 2.0 d 3.0**需要注意點的是:**利用標簽的切片運算與普通的Python切片運算不同,其末端是包含的:
In [125]: obj['b':'c'] Out[125]: b 1.0 c 2.0 dtype: float64用切片可以對Series的相應部分進行設置:
In [126]: obj['b':'c'] = 5 In [127]: obj Out[127]: a 0.0 b 5.0 c 5.0 d 3.0 dtype: float64用一個值或序列對DataFrame進行索引其實就是獲取一個或多個列:
In [128]: data = pd.DataFrame(np.arange(16).reshape((4, 4)), .....: index=['Ohio', 'Colorado', 'Utah','New York'], .....: columns=['one', 'two', 'three', 'four']) In [129]: data Out[129]: one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 Utah 8 9 10 11 New York 12 13 14 15 In [130]: data['two'] Out[130]: Ohio 1 Colorado 5 Utah 9 New York 13 Name: two, dtype: int64 In [131]: data[['three', 'one']] Out[131]: three one Ohio 2 0 Colorado 6 4 Utah 10 8 New York 14 12 In [132]: data[:2] Out[132]: one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 In [133]: data[data['three'] > 5] Out[133]: one two three four Colorado 4 5 6 7 Utah 8 9 10 11 New York 12 13 14 15用loc和iloc進行選取
對于DataFrame的行的標簽索引,我引入了特殊的標簽運算符loc和iloc。它們可以讓你用類似NumPy的標記,使用軸標簽(loc)或整數索引(iloc),從DataFrame選擇行和列的子集。
總結
以上是生活随笔為你收集整理的利用python进行数据分析第二版学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python中的if __name__
- 下一篇: AUC,KS