# 5/07、14 Python與資料分析#8 - Pandas ## Pandas概論 分析或處理資料的library 包含標記資料的data frame Python本身不容易處理表格資料 Pandas可以補強此弱點 Pandas =Panel(Deprecated since version 0.20.0)(不會用到) +DataFrame +Series 安裝以及使用 pip install pandas import pandas as pd 檢查版本以及位置屬性 print(pd.__version__) print(pd.__file__) 搜尋資料夾內東西 cd 路徑 ls ## Pandas主要結構 Index, ndarray, Series, and DataFrame Series:由Index以及ndarray的類別拼湊而成的對象 DataFrame:數個Series共享同一組Index形成的結構 ### Index 結合tuple(不可改)與set(可作集合運算)的特性 不可改-Persistence Storage:在資料上面貼標籤(類似流水號),表格形式的資料用兩個標籤(列/欄)代表一個資料 * 物件名稱 = pd.Index(一個list) (I需大寫) ``` prime_indices = pd.Index([2, 3, 5, 7, 11, 13, 17, 19, 23, 29]) print(type(prime_indices)) ``` <class 'pandas.core.indexes.numeric.Int64Index'> * 類似集合運算方式為index a.運算符號(index b) ``` # Index has the characteristics of a set odd_indices = pd.Index(range(1, 30, 2)) #以下為集合運算:交集/聯集/對稱差集/差集 print(prime_indices.intersection(odd_indices)) # prime_indices & odd_indices print(prime_indices.union(odd_indices)) # prime_indices | odd_indices print(prime_indices.symmetric_difference(odd_indices)) # prime_indices ^ odd_indices print(prime_indices.difference(odd_indices)) print(odd_indices.difference(prime_indices)) ``` ### Series * 物件名稱 = pd.Series(一個list) (S需大寫) ``` prime_series = pd.Series([2, 3, 5, 7, 11, 13, 17, 19, 23, 29]) print(type(prime_series)) print(prime_series) ``` 0 2 1 3 2 5 3 7 4 11 5 13 6 17 7 19 8 23 9 29 <class 'pandas.core.series.Series'> * Series組成會是Index和ndarray(類似Dictionary) ``` print(prime_series.index) print(prime_series.values) print(type(prime_series.index)) print(type(prime_series.values)) ``` RangeIndex(Start=0, Stop=10, Step=1) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] <class 'pandas.core.indexes.range.RangeIndex'> <class 'numpy.ndarray'> ### DataFrame * 物件名稱 = pd.DataFrame() (DF大寫) 此時內容空白 type(物件名稱) <class 'pandas.core.frame.DataFrame'> 物件名稱.shape (0,0) * 新增:欄位的概念 很多Series 共享同一個Index ``` movie_df = pd.DataFrame() movie_df["title"] = ["The Shawshank Redemption", "The Dark Knight", "Schindler's List", "Forrest Gump", "Inception"] movie_df["imdb_rating"] = [9.3, 9.0, 8.9, 8.8, 8.7] ``` ![](https://i.imgur.com/324w8Pg.png) 每個欄位都是一個Series 物件名稱[欄位名稱] 可以叫出該Series內容 ``` print(type(movie_df.index)) print(type(movie_df["title"])) print(type(movie_df["imdb_rating"])) ``` <class 'pandas.core.indexes.range.RangeIndex'> <class 'pandas.core.series.Series'> <class 'pandas.core.series.Series'> ## 資料科學的流程 ![](https://i.imgur.com/xigbdI4.png) Pandas包含: Importing Tidying Transforming(跟Tidying合稱Wrangling) ## Importing https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html 可以載入 Flat text file(csv) Database table(sql) Spreadsheet(excel) JSON HTML <table></table> tags ... 讀: read_格式 寫: to_格式 ### read_csv 物件名稱 = pd.read_csv("位置") 若為本機,括號直接輸入路徑即可,如果notebook處於該資料夾內,可直接輸入檔名,或是該資料夾以後的路徑即可 若為網路上資料,若有可以送get請求的權限,可以直接讀取,不用下載 在括號內輸入網址路徑 在github裡面該檔案按下raw可得到路徑 以下範例可以擷取當天的報告 會用到date類別以及timedelta函數 (在datetime這個library) 以及HTTPError (在urllib.error裡面) while True可以視為無窮迴圈 但try裡面有個break 成功讀取之後就break ``` from datetime import date from datetime import timedelta from urllib.error import HTTPError def get_latest_daily_report(): today = date.today() day_delta = timedelta(days=1) data_date = today while True: data_date_str = date.strftime(data_date, '%m-%d-%Y') print("Try importing {} data...".format(data_date_str)) daily_report_url = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/{}.csv".format(data_date_str) try: daily_report = pd.read_csv(daily_report_url) print("Successfully imported {} data!".format(data_date_str)) break except HTTPError: data_date -= day_delta return daily_report ``` 每筆資料都有兩個label對應 ![](https://i.imgur.com/7x4qN93.png) NAN代表兩個逗號之間沒東西 物件名稱.head(n) 可以輸出前n筆資料 ### read_sql 讀取資料庫 必須要有connection以及query ``` import sqlite3 conn = sqlite3.connect('YOUR_DATABASE.db') sql_query = """ SELECT * FROM YOUR_TABLE LIMIT 10; """ pd.read_sql(sql_query, conn) ``` ### read_excel 同csv 物件名稱 = pd.read_excel("位置") ### read_json 物件名稱 = pd.read_json("位置") json可以是key/value或是list或是兩者混搭的結構 Row-based JSON.(cloumn為key) 一列一列為單位紀錄,列出每列為key,常用list包裝最外面 Column-based JSON.(row為key) 一欄一欄為單位紀錄,列出每欄為key,常用dic包裝最外面 (原始樣貌不同,讀成表格的話可能相同) ### read_html pandas可以讀html檔案(或網址)的table標記 物件名稱 = pd.read_html("位置") 裡面可能有很多表格,用list來放很多DataFrame len就是表格數 可以用list相關語法操作(例如物件名稱[0]叫出第一個表格) ``` request_url = "https://www.imdb.com/chart/top" html_tables = pd.read_html(request_url) print(type(html_tables)) print(len(html_tables)) ``` <class 'list'> 1 ## DataFrame物件的基本屬性(attributes) shape dtypes index columns 使用方法皆為 物件名稱.屬性 shape會輸出(列數,欄數) dtypes(加s是因為不同欄位的type異質)會輸出每個欄位的資料型態 例如int64 float64 object(文字)... index是指列的名稱,用Index呈現 可能是list或是RangeIndex(....) columns會用Index呈現,列出所有的欄位名稱 ## DataFrame物件的基本方法(methods) head(n) tail(n) describe() info set_index reset_index 使用方法皆為 物件名稱.屬性() head(n)列出前n筆資料 tail(n)列出後n筆資料(一般預設會show出前後五筆資料) describe()則列出資料的一些描述性統計(形式是DataFrame) (count只計算非NAN值個數) ![](https://i.imgur.com/Cq5qD8h.png) info()則複合地整理這些資料的資訊 ![](https://i.imgur.com/esbLmVS.png) set_index("某欄位名稱") 將某個欄位的內容改成某個欄位的內容 reset_index() 將欄位回到預設值RangeIndex ## Basic Wrangling Wangling = Tidying + Transforming Selecting 挑選特定欄位 Filtering 挑選特定觀測值 Subset 挑選特定欄位以及觀測值 Indexing 透過絕對位置挑選資料 Sorting 排序資料 Deriving 生成新的欄位 Summarizing 聚合資料aggregate Summarizing and Grouping 分組聚合資料 ### 挑某一欄位 DataFrame名稱["欄位名稱"] 結果會是一個Series DataFrame名稱[["欄位名稱"]] 結果會是一個DataFrame ### 挑某些欄位 DataFrame名稱[["欄位名稱1", "欄位名稱2", ...]] 結果會是一個DataFrame ### 從條件挑觀測值(Index Filter) 原理: DataFrame名稱[Bol list] 就可以列出是True的列 Bol list可以寫成 DataFrame名稱["欄位名稱"]== "字串" (兩行可以合併) ### 取某個列欄(Subset) 合併上面挑欄位以及Index的指令 DataFrame名稱[列filter][欄位挑選] ### Indexing DataFrame名稱.loc[[index], ["欄位名稱"]] location (欄位名稱或index超過一個可以"再"用list包起來,可用:表示全部) DataFrame名稱.iloc[[index], [欄位編號]] int location (欄位編號或index超過一個可以用list包起來,可用:表示全部) *以上若分別沒有用中括號,結果就不會是DataFrame 此法類似上面的subset選取 但subset的方法,只能用index filter 若用subset的方法要用index的話,要先欄位再index且不能選擇某些index ### Sorting DataFrame名稱.sort_values("欄位",ascending = 預設True) True代表遞增排序,False代表遞減排序 若多個欄位包成一個list,則按照欄位順序為順位排序 ascending後面也用list包起來 DataFrame名稱.sort_index(ascending = 預設True) ### Series之間可以做向量化運算 ``` active = daily_report['Confirmed'] - daily_report['Deaths'] - daily_report['Recovered'] print(active) ``` ### 將數值變成類別 下面是將某欄位數值以0 1000 10000 100000 無限大(np.Inf)區分 pd.cut(DataFrame名稱['欄位'], bins=[所有切點], labels=[轉換成的類別], right=False代表不包含數值右邊) ``` import numpy as np cut_bins = [0, 1000, 10000, 100000, np.Inf] cut_labels = ['Less than 1000', 'Between 1000 and 10000', 'Between 10000 and 100000', 'Above 100000'] confirmed_categorical = pd.cut(daily_report['Confirmed'], bins=cut_bins, labels=cut_labels, right=False) print(confirmed_categorical) ``` 0 Between 10000 and 100000 1 Above 100000 2 Above 100000 3 Between 10000 and 100000 4 Between 10000 and 100000 ... 3978 Between 1000 and 10000 3979 Above 100000 3980 Between 1000 and 10000 3981 Between 10000 and 100000 3982 Between 10000 and 100000 Name: Confirmed, Length: 3983, dtype: category Categories (4, object): ['Less than 1000' < 'Between 1000 and 10000' < 'Between 10000 and 100000' < 'Above 100000'] ### 用map新增替換表格內容 這裡並沒有把原始內容更改,而是直接把改完後的內容列出來 如果需要另外利用,需要用變數去接 若要直接替換,變數則用原來的欄位名稱 (例如DataFrame名稱['欄位名'] = ....) * 用dict 替換的前後用dict包起來 dic名稱 = {更改前:更改後, ...} DataFrame名稱['表格所在的欄位'].map(替換的dict名稱) ``` # Passing a dict country_name = { 'Taiwan*': 'Taiwan' } daily_report_tw = daily_report[is_taiwan] #index filter叫出有Taiwan的列 daily_report_tw['Country_Region'].map(country_name) ``` * 用function 定義一個function將US變成US,其他變成not US DataFrame名稱['表格所在的欄位'].map(函數名稱) ** DataFrame名稱.value_counts() 可以總結各值的個數 ``` # Passing a function def is_us(x): if x == 'US': return 'US' else: return 'Not US' daily_report['Country_Region'].map(is_us) ``` * 用lambda DataFrame名稱['表格所在的欄位'].map(lambda函數) 例如上述可以寫成 lambda x: 'US' if x == 'US' else 'Not US' ### 摘要DataFrame欄位(aggregate聚合) DataFrame名稱['欄位'].sum() 某欄位總合 DataFrame名稱['欄位'].max() 某欄位最大值 DataFrame名稱['欄位'].min() 某欄位最小值 sum搭配Index filter可以知道某個資料的個數 因為True=1 False=0 若要把最大或最小值的列找出來 可以搭配Index filter ### 分組 DataFrame名稱.groupby("依據的欄位") (依據的欄位可以超過一個) 以上只是一個generator object 後面要再寫[["想聚合的欄位"...]].sum() 就可以把這些欄位加起來 會得到一個series 可以繼續操作(例如.sort_values) (若欄位超過一個就會得到DataFrame) ## Advanced Wrangling Dealing with missing values Dealing with text values Reshaping dataframes Merging and joining dataframes ### 處理Nan * 判斷 不能用 欄位 == np.nan判斷(因為都會是False) DataFrame名稱["欄位"].isnull() 會列出一個series,如果是Nan就會顯示True DataFrame名稱["欄位"].notnull() 會列出一個series,如果是Nan就會顯示False .sum就可以得到Nan或非Nan筆數 * 處理 DataFrame名稱.dropnan() 若該列有一個Nan就會將該列整個移除 若裡面輸入axis=1代表檢查攔(預設0) 若裡面輸入how="all"代表要全部Nan才會移除(預設any) DataFrame名稱["欄位名稱"].fillna(n) 將該欄位的Nan改成n(可以是字串"") ### 摘要文字資料 (數值資料可以用.describe()) DataFrame名稱["欄位名稱"].nunique() 輸出獨立值個數 DataFrame名稱["欄位名稱"].unique() 輸出全部獨立值 DataFrame名稱["欄位名稱"].value_counts() 列出每個獨立值出現次數 ### 處理文字資料 * 有時候同一個欄位可能塞入多種資料(不是tidy data) DataFrame名稱["欄位名稱"].str.split() 可以將字串切開(預設以空格為切開依據,也可以()輸入"."表示用.分開) 內容是一個series,裡面是list 若()輸入expand = True則會用DataFrame顯示 0 [1., 刺激1995, (1994)] 1 [2., 教父, (1972)] 2 [3., 教父第二集, (1974)] 3 [4., 黑暗騎士, (2008)] 4 [5., 十二怒漢, (1957)] * DataFrame名稱["欄位名稱"].str.replace("a", "b") 將該欄位字串中的a改成b 若想把"("或")"改成空白 則可使用 str.replace("\(|\)", '') \(意思就是跳脫有意義的(str.replace("\(|\)", ''),|則是or的意思 * DataFrame名稱["欄位名稱"].str.astype(int) 將形態改成int * DataFrame名稱["欄位名稱"].str.contains("字串") 模糊比對,只要有包含就會出現 呈現方式是個True/False的Series(類似用 == 比對的) 外面可以用DataFrame名稱[]包起來 即可把這些列列出來 再加上["欄位名稱"].unique 可以把這些欄位叫出來 ### 調整資料外型 若要把各欄的資料變成列,可以使用melt函數 例如 ![](https://i.imgur.com/bLu0VeF.png) 變成 ![](https://i.imgur.com/gzwlRnX.png) 本來欄位內容變成Date欄位放在格子內 本來欄位的內容改成Confirmed欄位 var_name: Let's name it Date pd.melt(DataFrame名稱, id_vars=[不動的欄位], value_vars=[要動的欄位],var_name="要動的欄位改成的欄位名稱", value_name="本來的內容改成的欄位名稱") 若要動的欄位是id_vars的除外欄位,可以不用省略 ### 結合資料 * pd.merge(DataFrame名稱1,名稱2) (函數)依照欄位名稱 預設取兩個DataFrame的交集(預設)合併 (index跟欄位不一定要一樣多) *加上 how = "left"或"right" 代表以左或右的列為基準 how = "outer" 則取聯集(預設交集outer) 結合之後或有些內容沒有的就NAN * DataFrame名稱1.join(DataFrame名稱2)(方法)依照index 直接根據index名稱左右合併,預設左邊的列數為依據 (若以右邊資料為依據則how = "right",也可以輸入outer或innter) 若列數不一樣則以NAN表示 若原本欄位有名稱相同,需要寫 lsuffix="左綴字內容", rsuffix="右綴字內容" 若不想只是用index內容合併,可使用.set_index("欄位名稱") ###### tags: `python` `資料分析`