# 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]
```

每個欄位都是一個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'>
## 資料科學的流程

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對應

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值個數)

info()則複合地整理這些資料的資訊

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函數
例如

變成

本來欄位內容變成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` `資料分析`