教你學會 Pandas 不是我的目的,教你輕鬆玩轉 Pandas 才是我的目的。我會通過一系列例項來帶入 Pandas 的知識點,讓你在學習 Pandas 的路上不再枯燥。
宣告:我所寫的輕鬆玩轉 Pandas 教程都是免費的,如果對你有幫助,你可以持續關注我。
在 Pandas資料結構詳解 | 輕鬆玩轉Pandas(1) 介紹了 Pandas 中常用的兩種資料結構 Series 以及 DataFrame,這裡來看下這些資料結構都有哪些常用的功能。
# 匯入相關庫
import numpy as np
import pandas as pd
複製程式碼
常用的基本功能
當我們構建好了 Series 和 DataFrame 之後,我們會經常使用哪些功能呢?來跟我看看吧。引用上一章節中的場景,我們有一些使用者的的資訊,並將它們儲存到了 DataFrame 中。
因為大多數情況下 DataFrame 比 Series 更為常用,所以這裡以 DataFrame 舉例說明,但實際上很多常用功能對於 Series 也適用。
index = pd.Index(data=["Tom", "Bob", "Mary", "James"], name="name")
data = {
"age": [18, 30, 25, 40],
"city": ["BeiJing", "ShangHai", "GuangZhou", "ShenZhen"],
"sex": ["male", "male", "female", "male"]
}
user_info = pd.DataFrame(data=data, index=index)
user_info
複製程式碼
age | city | sex | |
---|---|---|---|
name | |||
Tom | 18 | BeiJing | male |
Bob | 30 | ShangHai | male |
Mary | 25 | GuangZhou | female |
James | 40 | ShenZhen | male |
一般拿到資料,我們第一步需要做的是瞭解下資料的整體情況,可以使用 info
方法來檢視。
user_info.info()
複製程式碼
Index: 4 entries, Tom to James
Data columns (total 3 columns):
age 4 non-null int64
city 4 non-null object
sex 4 non-null object
dtypes: int64(1), object(2)
memory usage: 128.0+ bytes
複製程式碼
如果我們的資料量非常大,我想看看資料長啥樣,我當然不希望檢視所有的資料了,這時候我們可以採用只看頭部的 n 條或者尾部的 n 條。檢視頭部的 n 條資料可以使用 head
方法,檢視尾部的 n 條資料可以使用 tail
方法。
user_info.head(2)
複製程式碼
age | city | sex | |
---|---|---|---|
name | |||
Tom | 18 | BeiJing | male |
Bob | 30 | ShangHai | male |
此外,Pandas 中的資料結構都有 ndarray 中的常用方法和屬性,如通過 .shape
獲取資料的形狀,通過 .T
獲取資料的轉置。
user_info.shape
複製程式碼
(4, 3)
複製程式碼
user_info.T
複製程式碼
name | Tom | Bob | Mary | James |
---|---|---|---|---|
age | 18 | 30 | 25 | 40 |
city | BeiJing | ShangHai | GuangZhou | ShenZhen |
sex | male | male | female | male |
如果我們想要通過 DataFrame 來獲取它包含的原有資料,可以通過 .values
來獲取,獲取後的資料型別其實是一個 ndarray。
user_info.values
複製程式碼
array([[18, 'BeiJing', 'male'],
[30, 'ShangHai', 'male'],
[25, 'GuangZhou', 'female'],
[40, 'ShenZhen', 'male']], dtype=object)
複製程式碼
描述與統計
有時候我們獲取到資料之後,想要檢視下資料的簡單統計指標(最大值、最小值、平均值、中位數等),比如想要檢視年齡的最大值,如何實現呢?
直接對 age
這一列呼叫 max
方法即可。
user_info.age.max()
複製程式碼
40
複製程式碼
類似的,通過呼叫 min
、mean
、quantile
、sum
方法可以實現最小值、平均值、中位數以及求和。可以看到,對一個 Series
呼叫 這幾個方法之後,返回的都只是一個聚合結果。
來介紹個有意思的方法:cumsum
,看名字就發現它和 sum
方法有關係,事實上確實如此,cumsum
也是用來求和的,不過它是用來累加求和的,也就是說它得到的結果與原始的 Series
或 DataFrame
大小相同。
user_info.age.cumsum()
複製程式碼
name
Tom 18
Bob 48
Mary 73
James 113
Name: age, dtype: int64
複製程式碼
可以看到,cummax
最後的結果就是將上一次求和的結果與原始當前值求和作為當前值。這話聽起來有點繞。舉個例子,上面的 73 = 48 + 25
。cumsum
也可以用來操作字串型別的物件。
user_info.sex.cumsum()
複製程式碼
name
Tom male
Bob malemale
Mary malemalefemale
James malemalefemalemale
Name: sex, dtype: object
複製程式碼
如果想要獲取更多的統計方法,可以參見官方連結:Descriptive statistics
雖然說常見的各種統計值都有對應的方法,如果我想要得到多個指標的話,就需要呼叫多次方法,是不是顯得有點麻煩呢?
Pandas 設計者自然也考慮到了這個問題,想要一次性獲取多個統計指標,只需呼叫 describe
方法即可。
user_info.describe()
複製程式碼
age | |
---|---|
count | 4.000000 |
mean | 28.250000 |
std | 9.251126 |
min | 18.000000 |
25% | 23.250000 |
50% | 27.500000 |
75% | 32.500000 |
max | 40.000000 |
可以看到,直接呼叫 describe
方法後,會顯示出數字型別的列的一些統計指標,如 總數、平均數、標準差、最小值、最大值、25%/50%/75% 分位數。如果想要檢視非數字型別的列的統計指標,可以設定 include=["object"] 來獲得。
user_info.describe(include=["object"])
複製程式碼
city | sex | |
---|---|---|
count | 4 | 4 |
unique | 4 | 2 |
top | BeiJing | male |
freq | 1 | 3 |
上面的結果展示了非數字型別的列的一些統計指標:總數,去重後的個數、最常見的值、最常見的值的頻數。
此外,如果我想要統計下某列中每個值出現的次數,如何快速實現呢?呼叫 value_counts
方法快速獲取 Series
中每個值出現的次數。
user_info.sex.value_counts()
複製程式碼
male 3
female 1
Name: sex, dtype: int64
複製程式碼
如果想要獲取某列最大值或最小值對應的索引,可以使用 idxmax
或 idxmin
方法完成。
user_info.age.idxmax()
複製程式碼
'James'
複製程式碼
離散化
有時候,我們會碰到這樣的需求,想要將年齡進行離散化(分桶),直白來說就是將年齡分成幾個區間,這裡我們想要將年齡分成 3 個區間段。就可以使用 Pandas 的 cut
方法來完成。
pd.cut(user_info.age, 3)
複製程式碼
name
Tom (17.978, 25.333]
Bob (25.333, 32.667]
Mary (17.978, 25.333]
James (32.667, 40.0]
Name: age, dtype: category
Categories (3, interval[float64]): [(17.978, 25.333] < (25.333, 32.667] < (32.667, 40.0]]
複製程式碼
可以看到, cut
自動生成了等距的離散區間,如果自己想定義也是沒問題的。
pd.cut(user_info.age, [1, 18, 30, 50])
複製程式碼
name
Tom (1, 18]
Bob (18, 30]
Mary (18, 30]
James (30, 50]
Name: age, dtype: category
Categories (3, interval[int64]): [(1, 18] < (18, 30] < (30, 50]]
複製程式碼
有時候離散化之後,想要給每個區間起個名字,可以指定 labels 引數。
pd.cut(user_info.age, [1, 18, 30, 50], labels=["childhood", "youth", "middle"])
複製程式碼
name
Tom childhood
Bob youth
Mary youth
James middle
Name: age, dtype: category
Categories (3, object): [childhood < youth < middle]
複製程式碼
除了可以使用 cut
進行離散化之外,qcut
也可以實現離散化。cut
是根據每個值的大小來進行離散化的,qcut
是根據每個值出現的次數來進行離散化的。
pd.qcut(user_info.age, 3)
複製程式碼
name
Tom (17.999, 25.0]
Bob (25.0, 30.0]
Mary (17.999, 25.0]
James (30.0, 40.0]
Name: age, dtype: category
Categories (3, interval[float64]): [(17.999, 25.0] < (25.0, 30.0] < (30.0, 40.0]]
複製程式碼
排序功能
在進行資料分析時,少不了進行資料排序。Pandas 支援兩種排序方式:按軸(索引或列)排序和按實際值排序。
先來看下按索引排序:sort_index
方法預設是按照索引進行正序排的。
user_info.sort_index()
複製程式碼
age | city | sex | |
---|---|---|---|
name | |||
Bob | 30 | ShangHai | male |
James | 40 | ShenZhen | male |
Mary | 25 | GuangZhou | female |
Tom | 18 | BeiJing | male |
如果想要按照列進行倒序排,可以設定引數 axis=1
和 ascending=False
。
user_info.sort_index(axis=1, ascending=False)
複製程式碼
sex | city | age | |
---|---|---|---|
name | |||
Tom | male | BeiJing | 18 |
Bob | male | ShangHai | 30 |
Mary | female | GuangZhou | 25 |
James | male | ShenZhen | 40 |
如果想要實現按照實際值來排序,例如想要按照年齡排序,如何實現呢?
使用 sort_values
方法,設定引數 by="age"
即可。
user_info.sort_values(by="age")
複製程式碼
age | city | sex | |
---|---|---|---|
name | |||
Tom | 18 | BeiJing | male |
Mary | 25 | GuangZhou | female |
Bob | 30 | ShangHai | male |
James | 40 | ShenZhen | male |
有時候我們可能需要按照多個值來排序,例如:按照年齡和城市來一起排序,可以設定引數 by 為一個 list 即可。
注意:list 中每個元素的順序會影響排序優先順序的。
user_info.sort_values(by=["age", "city"])
複製程式碼
age | city | sex | |
---|---|---|---|
name | |||
Tom | 18 | BeiJing | male |
Mary | 25 | GuangZhou | female |
Bob | 30 | ShangHai | male |
James | 40 | ShenZhen | male |
一般在排序後,我們可能需要獲取最大的n個值或最小值的n個值,我們可以使用 nlargest
和 nsmallest
方法來完成,這比先進行排序,再使用 head(n)
方法快得多。
user_info.age.nlargest(2)
複製程式碼
name
James 40
Bob 30
Name: age, dtype: int64
複製程式碼
函式應用
雖說 Pandas 為我們提供了非常豐富的函式,有時候我們可能需要自己定製一些函式,並將它應用到 DataFrame 或 Series。常用到的函式有:map
、apply
、applymap
。
map
是 Series 中特有的方法,通過它可以對 Series 中的每個元素實現轉換。
如果我想通過年齡判斷使用者是否屬於中年人(30歲以上為中年),通過 map
可以輕鬆搞定它。
# 接收一個 lambda 函式
user_info.age.map(lambda x: "yes" if x >= 30 else "no")
複製程式碼
name
Tom no
Bob yes
Mary no
James yes
Name: age, dtype: object
複製程式碼
又比如,我想要通過城市來判斷是南方還是北方,我可以這樣操作。
city_map = {
"BeiJing": "north",
"ShangHai": "south",
"GuangZhou": "south",
"ShenZhen": "south"
}
# 傳入一個 map
user_info.city.map(city_map)
複製程式碼
name
Tom north
Bob south
Mary south
James south
Name: city, dtype: object
複製程式碼
apply
方法既支援 Series,也支援 DataFrame,在對 Series 操作時會作用到每個值上,在對 DataFrame 操作時會作用到所有行或所有列(通過 axis
引數控制)。
# 對 Series 來說,apply 方法 與 map 方法區別不大。
user_info.age.apply(lambda x: "yes" if x >= 30 else "no")
複製程式碼
name
Tom no
Bob yes
Mary no
James yes
Name: age, dtype: object
複製程式碼
# 對 DataFrame 來說,apply 方法的作用物件是一行或一列資料(一個Series)
user_info.apply(lambda x: x.max(), axis=0)
複製程式碼
age 40
city ShenZhen
sex male
dtype: object
複製程式碼
applymap
方法針對於 DataFrame,它作用於 DataFrame 中的每個元素,它對 DataFrame 的效果類似於 apply
對 Series 的效果。
user_info.applymap(lambda x: str(x).lower())
複製程式碼
age | city | sex | |
---|---|---|---|
name | |||
Tom | 18 | beijing | male |
Bob | 30 | shanghai | male |
Mary | 25 | guangzhou | female |
James | 40 | shenzhen | male |
修改列/索引名稱
在使用 DataFrame 的過程中,經常會遇到修改列名,索引名等情況。使用 rename
輕鬆可以實現。
修改列名只需要設定引數 columns
即可。
user_info.rename(columns={"age": "Age", "city": "City", "sex": "Sex"})
複製程式碼
Age | City | Sex | |
---|---|---|---|
name | |||
Tom | 18 | BeiJing | male |
Bob | 30 | ShangHai | male |
Mary | 25 | GuangZhou | female |
James | 40 | ShenZhen | male |
類似的,修改索引名只需要設定引數 index
即可。
user_info.rename(index={"Tom": "tom", "Bob": "bob"})
複製程式碼
age | city | sex | |
---|---|---|---|
name | |||
tom | 18 | BeiJing | male |
bob | 30 | ShangHai | male |
Mary | 25 | GuangZhou | female |
James | 40 | ShenZhen | male |
型別操作
如果想要獲取每種型別的列數的話,可以使用 get_dtype_counts
方法。
user_info.get_dtype_counts()
複製程式碼
int64 1
object 2
dtype: int64
複製程式碼
如果想要轉換資料型別的話,可以通過 astype
來完成。
user_info["age"].astype(float)
複製程式碼
name
Tom 18.0
Bob 30.0
Mary 25.0
James 40.0
Name: age, dtype: float64
複製程式碼
有時候會涉及到將 object 型別轉為其他型別,常見的有轉為數字、日期、時間差,Pandas 中分別對應 to_numeric
、to_datetime
、to_timedelta
方法。
這裡給這些使用者都新增一些關於身高的資訊。
user_info["height"] = ["178", "168", "178", "180cm"]
user_info
複製程式碼
age | city | sex | height | |
---|---|---|---|---|
name | ||||
Tom | 18 | BeiJing | male | 178 |
Bob | 30 | ShangHai | male | 168 |
Mary | 25 | GuangZhou | female | 178 |
James | 40 | ShenZhen | male | 180cm |
現在將身高這一列轉為數字,很明顯,180cm 並非數字,為了強制轉換,我們可以傳入 errors
引數,這個引數的作用是當強轉失敗時的處理方式。
預設情況下,errors='raise'
,這意味著強轉失敗後直接丟擲異常,設定 errors='coerce'
可以在強轉失敗時將有問題的元素賦值為 pd.NaT(對於datetime和timedelta)或 np.nan(數字)。設定 errors='ignore'
可以在強轉失敗時返回原有的資料。
pd.to_numeric(user_info.height, errors="coerce")
複製程式碼
name
Tom 178.0
Bob 168.0
Mary 178.0
James NaN
Name: height, dtype: float64
複製程式碼
pd.to_numeric(user_info.height, errors="ignore")
複製程式碼
name
Tom 178
Bob 168
Mary 178
James 180cm
Name: height, dtype: object
複製程式碼
想要學習更多關於人工智慧的知識,請關注公眾號:AI派
這裡我將整篇文章的內容整理成了pdf,想要pdf檔案的可以在公眾號後臺回覆關鍵字:pandas02。
更多Pandas知識見:輕鬆玩轉Pandas