---
# System prepended metadata

title: 5/07、14 Python與資料分析#8 - Pandas
tags: [python, 資料分析]

---

# 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` `資料分析`

