Python 100 Days Day71 Introduction to Pandas-2
26 Sep 2022 | beginner pythonauthor: jackfrued
Pandas的應用-2
DataFrame的應用
創建DataFrame對象
通過二維數組創建DataFrame對象
代碼:
scores = np.random.randint(60, 101, (5, 3))
courses = ['語文', '數學', '英語']
ids = [1001, 1002, 1003, 1004, 1005]
df1 = pd.DataFrame(data=scores, columns=courses, index=ids)
df1
輸出:
語文 數學 英語
1001 69 80 79
1002 71 60 100
1003 94 81 93
1004 88 88 67
1005 82 66 60
通過字典創建DataFrame對象
代碼:
scores = {
'語文': [62, 72, 93, 88, 93],
'數學': [95, 65, 86, 66, 87],
'英語': [66, 75, 82, 69, 82],
}
ids = [1001, 1002, 1003, 1004, 1005]
df2 = pd.DataFrame(data=scores, index=ids)
df2
輸出:
語文 數學 英語
1001 69 80 79
1002 71 60 100
1003 94 81 93
1004 88 88 67
1005 82 66 60
讀取 CSV 文件創建DataFrame對象
可以通過pandas 模塊的read_csv函數來讀取 CSV 文件,read_csv函數的參數非常多,下面接受幾個比較重要的參數。
sep/delimiter:分隔符,默認是,。header:表頭(列索引)的位置,默認值是infer,用第一行的內容作為表頭(列索引)。index_col:用作行索引(標簽)的列。usecols:需要加載的列,可以使用序號或者列名。true_values/false_values:哪些值被視為布爾值True/False。skiprows:通過行號、索引或函數指定需要跳過的行。skipfooter:要跳過的末尾行數。nrows:需要讀取的行數。na_values:哪些值被視為空值。
代碼:
df3 = pd.read_csv('2018年北京積分落戶數據.csv', index_col='id')
df3
輸出:
name birthday company score
id
1 楊x 1972-12 北京利德xxxx 122.59
2 紀x 1974-12 北京航天xxxx 121.25
3 王x 1974-05 品牌聯盟xxxx 118.96
4 楊x 1975-07 中科專利xxxx 118.21
5 張x 1974-11 北京阿里xxxx 117.79
... ... ... ... ...
6015 孫x 1978-08 華為海洋xxxx 90.75
6016 劉x 1976-11 福斯流體xxxx 90.75
6017 周x 1977-10 贏創德固xxxx 90.75
6018 趙x 1979-07 澳科利耳xxxx 90.75
6019 賀x 1981-06 北京寶潔xxxx 90.75
6019 rows × 4 columns
說明:如果需要上面例子中的 CSV 文件,可以通過下面的百度雲盤地址進行獲取,數據在《從零開始學數據分析》目錄中。鏈接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g,提取碼:e7b4。
讀取Excel文件創建DataFrame對象
可以通過pandas 模塊的read_excel函數來讀取 Exce l文件,該函數與上面的read_csv非常相近,多了一個sheet_name參數來指定數據表的名稱,但是不同於 CSV 文件,沒有sep或delimiter這樣的參數。下面的代碼中,read_excel函數的skiprows參數是一個 Lambda 函數,通過該 Lambda 函數指定只讀取 Excel 文件的表頭和其中10%的數據,跳過其他的數據。
代碼:
import random
df4 = pd.read_excel(
io='小寶劍大藥房2018年銷售數據.xlsx',
usecols=['購藥時間', '社保卡號', '商品名稱', '銷售數量', '應收金額', '實收金額'],
skiprows=lambda x: x > 0 and random.random() > 0.1
)
df4
說明:如果需要上面例子中的 Excel 文件,可以通過下面的百度雲盤地址進行獲取,數據在《從零開始學數據分析》目錄中。鏈接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g,提取碼:e7b4。
輸出:
購藥時間 社保卡號 商品名稱 銷售數量 應收金額 實收金額
0 2018-03-23 星期三 10012157328 強力xx片 1 13.8 13.80
1 2018-07-12 星期二 108207828 強力xx片 1 13.8 13.80
2 2018-01-17 星期日 13358228 清熱xx液 1 28.0 28.00
3 2018-07-11 星期一 10031402228 三九xx靈 5 149.0 130.00
4 2018-01-20 星期三 10013340328 三九xx靈 3 84.0 73.92
... ... ... ... ... ... ...
618 2018-03-05 星期六 10066059228 開博xx通 2 56.0 49.28
619 2018-03-22 星期二 10035514928 開博xx通 1 28.0 25.00
620 2018-04-15 星期五 1006668328 開博xx通 2 56.0 50.00
621 2018-04-24 星期日 10073294128 高特xx靈 1 5.6 5.60
622 2018-04-24 星期日 10073294128 高特xx靈 10 56.0 56.0
623 rows × 6 columns
通過SQL從數據庫讀取數據創建DataFrame對象
pandas模塊的read_sql函數可以通過 SQL 語句從數據庫中讀取數據創建DataFrame對象,該函數的第二個參數代表了需要連接的數據庫。對於 MySQL 數據庫,我們可以通過pymysql或mysqlclient來創建數據庫連接,得到一個Connection 對象,而這個對象就是read_sql函數需要的第二個參數,代碼如下所示。
代碼:
import pymysql
# 創建一個MySQL數據庫的連接對象
conn = pymysql.connect(
host='47.104.31.138', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4'
)
# 通過SQL從數據庫讀取數據創建DataFrame
df5 = pd.read_sql('select * from tb_emp', conn, index_col='eno')
df5
提示:執行上面的代碼需要先安裝
pymysql庫,如果尚未安裝,可以先在 Notebook 的單元格中先執行!pip install pymysql,然後再運行上面的代碼。上面的代碼連接的是我部署在阿里雲上的 MySQL 數據庫,公網 IP 地址:47.104.31.138,用戶名:guest,密碼:Guest.618,數據庫:hrs,表名:tb_emp,字符集:utf8mb4,大家可以使用這個數據庫,但是不要進行惡意的訪問。
輸出:
ename job mgr sal comm dno
eno
1359 胡一刀 銷售員 3344.0 1800 200.0 30
2056 喬峰 分析師 7800.0 5000 1500.0 20
3088 李莫愁 設計師 2056.0 3500 800.0 20
3211 張無忌 程序員 2056.0 3200 NaN 20
3233 丘處機 程序員 2056.0 3400 NaN 20
3244 歐陽鋒 程序員 3088.0 3200 NaN 20
3251 張翠山 程序員 2056.0 4000 NaN 20
3344 黃蓉 銷售主管 7800.0 3000 800.0 30
3577 楊過 會計 5566.0 2200 NaN 10
3588 朱九真 會計 5566.0 2500 NaN 10
4466 苗人鳳 銷售員 3344.0 2500 NaN 30
5234 郭靖 出納 5566.0 2000 NaN 10
5566 宋遠橋 會計師 7800.0 4000 1000.0 10
7800 張三豐 總裁 NaN 9000 1200.0 20
基本屬性和方法
在開始講解DataFrame的屬性和方法前,我們先從之前提到的hrs數據庫中讀取三張表的數據,創建出三個DataFrame對象,代碼如下所示。
import pymysql
conn = pymysql.connect(
host='47.104.31.138', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4'
)
dept_df = pd.read_sql('select * from tb_dept', conn, index_col='dno')
emp_df = pd.read_sql('select * from tb_emp', conn, index_col='eno')
emp2_df = pd.read_sql('select * from tb_emp2', conn, index_col='eno')
得到的三個DataFrame對象如下所示。
部門表(dept_df),其中dno是部門的編號,dname和dloc分別是部門的名稱和所在地。
dname dloc
dno
10 會計部 北京
20 研發部 成都
30 銷售部 重慶
40 運維部 天津
員工表(emp_df),其中eno是員工編號,ename、job、mgr、sal、comm和dno分別代表員工的姓名、職位、主管編號、月薪、補貼和部門編號。
ename job mgr sal comm dno
eno
1359 胡一刀 銷售員 3344.0 1800 200.0 30
2056 喬峰 分析師 7800.0 5000 1500.0 20
3088 李莫愁 設計師 2056.0 3500 800.0 20
3211 張無忌 程序員 2056.0 3200 NaN 20
3233 丘處機 程序員 2056.0 3400 NaN 20
3244 歐陽鋒 程序員 3088.0 3200 NaN 20
3251 張翠山 程序員 2056.0 4000 NaN 20
3344 黃蓉 銷售主管 7800.0 3000 800.0 30
3577 楊過 會計 5566.0 2200 NaN 10
3588 朱九真 會計 5566.0 2500 NaN 10
4466 苗人鳳 銷售員 3344.0 2500 NaN 30
5234 郭靖 出納 5566.0 2000 NaN 10
5566 宋遠橋 會計師 7800.0 4000 1000.0 10
7800 張三豐 總裁 NaN 9000 1200.0 20
說明:在數據庫中
mgr和comm兩個列的數據類型是int,但是因為有缺失值(空值),讀取到DataFrame之後,列的數據類型變成了float,因為我們通常會用float類型的NaN來表示空值。
員工表(emp2_df),跟上面的員工表結構相同,但是保存了不同的員工數據。
ename job mgr sal comm dno
eno
9800 駱昊 架構師 7800 30000 5000 20
9900 王小刀 程序員 9800 10000 1200 20
9700 王大錘 程序員 9800 8000 600 20
DataFrame對象的屬性如下表所示。
| 屬性名 | 說明 |
|---|---|
at / iat |
通過標簽獲取DataFrame中的單個值。 |
columns |
DataFrame對象列的索引 |
dtypes |
DataFrame對象每一列的數據類型 |
empty |
DataFrame對象是否為空 |
loc / iloc |
通過標簽獲取DataFrame中的一組值。 |
ndim |
DataFrame對象的維度 |
shape |
DataFrame對象的形狀(行數和列數) |
size |
DataFrame對象中元素的個數 |
values |
DataFrame對象的數據對應的二維數組 |
關於DataFrame的方法,首先需要了解的是info()方法,它可以幫助我們了解DataFrame的相關信息,如下所示。
代碼:
emp_df.info()
輸出:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 14 entries, 1359 to 7800
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ename 14 non-null object
1 job 14 non-null object
2 mgr 13 non-null float64
3 sal 14 non-null int64
4 comm 6 non-null float64
5 dno 14 non-null int64
dtypes: float64(2), int64(2), object(2)
memory usage: 1.3+ KB
如果需要查看DataFrame的頭部或尾部的數據,可以使用head()或tail()方法,這兩個方法的默認參數是5,表示獲取DataFrame最前面5行或最後面5行的數據,如下所示。
emp_df.head()
輸出:
ename job mgr sal comm dno
eno
1359 胡一刀 銷售員 3344 1800 200 30
2056 喬峰 分析師 7800 5000 1500 20
3088 李莫愁 設計師 2056 3500 800 20
3211 張無忌 程序員 2056 3200 NaN 20
3233 丘處機 程序員 2056 3400 NaN 20
獲取數據
索引和切片
如果要獲取DataFrame的某一列,例如取出上面emp_df的ename列,可以使用下面的兩種方式。
emp_df.ename
或者
emp_df['ename']
執行上面的代碼可以發現,我們獲得的是一個Series對象。事實上,DataFrame對象就是將多個Series對象組合到一起的結果。
如果要獲取DataFrame的某一行,可以使用整數索引或我們設置的索引,例如取出員工編號為2056的員工數據,代碼如下所示。
emp_df.iloc[1]
或者
emp_df.loc[2056]
通過執行上面的代碼我們發現,單獨取DataFrame 的某一行或某一列得到的都是Series對象。我們當然也可以通過花式索引來獲取多個行或多個列的數據,花式索引的結果仍然是一個DataFrame對象。
獲取多個列:
emp_df[['ename', 'job']]
獲取多個行:
emp_df.loc[[2056, 7800, 3344]]
如果要獲取或修改DataFrame 對象某個單元格的數據,需要同時指定行和列的索引,例如要獲取員工編號為2056的員工的職位信息,代碼如下所示。
emp_df['job'][2056]
或者
emp_df.loc[2056]['job']
或者
emp_df.loc[2056, 'job']
我們推薦大家使用第三種做法,因為它只做了一次索引運算。如果要將該員工的職位修改為“架構師”,可以使用下面的代碼。
emp_df.loc[2056, 'job'] = '架構師'
當然,我們也可以通過切片操作來獲取多行多列,相信大家一定已經想到了這一點。
emp_df.loc[2056:3344]
輸出:
ename job mgr sal comm dno
eno
2056 喬峰 分析師 7800.0 5000 1500.0 20
3088 李莫愁 設計師 2056.0 3500 800.0 20
3211 張無忌 程序員 2056.0 3200 NaN 20
3233 丘處機 程序員 2056.0 3400 NaN 20
3244 歐陽鋒 程序員 3088.0 3200 NaN 20
3251 張翠山 程序員 2056.0 4000 NaN 20
3344 黃蓉 銷售主管 7800.0 3000 800.0 30
數據篩選
上面我們提到了花式索引,相信大家已經聯想到了布爾索引。跟ndarray和Series一樣,我們可以通過布爾索引對DataFrame對象進行數據篩選,例如我們要從emp_df中篩選出月薪超過3500的員工,代碼如下所示。
emp_df[emp_df.sal > 3500]
輸出:
ename job mgr sal comm dno
eno
2056 喬峰 分析師 7800.0 5000 1500.0 20
3251 張翠山 程序員 2056.0 4000 NaN 20
5566 宋遠橋 會計師 7800.0 4000 1000.0 10
7800 張三豐 總裁 NaN 9000 1200.0 20
當然,我們也可以組合多個條件來進行數據篩選,例如從emp_df中篩選出月薪超過3500且部門編號為20的員工,代碼如下所示。
emp_df[(emp_df.sal > 3500) & (emp_df.dno == 20)]
輸出:
ename job mgr sal comm dno
eno
2056 喬峰 分析師 7800.0 5000 1500.0 20
3251 張翠山 程序員 2056.0 4000 NaN 20
7800 張三豐 總裁 NaN 9000 1200.0 20
除了使用布爾索引,DataFrame對象的query方法也可以實現數據篩選,query方法的參數是一個字符串,它代表了篩選數據使用的表達式,而且更符合 Python 程序員的使用習慣。下面我們使用query方法將上面的效果重新實現一遍,代碼如下所示。
emp_df.query('sal > 3500 and dno == 20')
重塑數據
有的時候,我們做數據分析需要的原始數據可能並不是來自一個地方,就像上面的例子中,我們從關系型數據庫中讀取了三張表,得到了三個DataFrame對象,但實際工作可能需要我們把他們的數據整合到一起。例如:emp_df和emp2_df其實都是員工的數據,而且數據結構完全一致,我們可以使用pandas提供的concat函數實現兩個或多個DataFrame的數據拼接,代碼如下所示。
all_emp_df = pd.concat([emp_df, emp2_df])
輸出:
ename job mgr sal comm dno
eno
1359 胡一刀 銷售員 3344.0 1800 200.0 30
2056 喬峰 分析師 7800.0 5000 1500.0 20
3088 李莫愁 設計師 2056.0 3500 800.0 20
3211 張無忌 程序員 2056.0 3200 NaN 20
3233 丘處機 程序員 2056.0 3400 NaN 20
3244 歐陽鋒 程序員 3088.0 3200 NaN 20
3251 張翠山 程序員 2056.0 4000 NaN 20
3344 黃蓉 銷售主管 7800.0 3000 800.0 30
3577 楊過 會計 5566.0 2200 NaN 10
3588 朱九真 會計 5566.0 2500 NaN 10
4466 苗人鳳 銷售員 3344.0 2500 NaN 30
5234 郭靖 出納 5566.0 2000 NaN 10
5566 宋遠橋 會計師 7800.0 4000 1000.0 10
7800 張三豐 總裁 NaN 9000 1200.0 20
9800 駱昊 架構師 7800.0 30000 5000.0 20
9900 王小刀 程序員 9800.0 10000 1200.0 20
9700 王大錘 程序員 9800.0 8000 600.0 20
上面的代碼將兩個代表員工數據的DataFrame拼接到了一起,接下來我們使用merge函數將員工表和部門表的數據合並到一張表中,代碼如下所示。
先使用reset_index方法重新設置all_emp_df的索引,這樣eno 不再是索引而是一個普通列,reset_index方法的inplace參數設置為True表示,重置索引的操作直接在all_emp_df上執行,而不是返回修改後的新對象。
all_emp_df.reset_index(inplace=True)
通過merge函數合並數據,當然,也可以調用DataFrame對象的merge方法來達到同樣的效果。
pd.merge(dept_df, all_emp_df, how='inner', on='dno')
輸出:
dno dname dloc eno ename job mgr sal comm
0 10 會計部 北京 3577 楊過 會計 5566.0 2200 NaN
1 10 會計部 北京 3588 朱九真 會計 5566.0 2500 NaN
2 10 會計部 北京 5234 郭靖 出納 5566.0 2000 NaN
3 10 會計部 北京 5566 宋遠橋 會計師 7800.0 4000 1000.0
4 20 研發部 成都 2056 喬峰 架構師 7800.0 5000 1500.0
5 20 研發部 成都 3088 李莫愁 設計師 2056.0 3500 800.0
6 20 研發部 成都 3211 張無忌 程序員 2056.0 3200 NaN
7 20 研發部 成都 3233 丘處機 程序員 2056.0 3400 NaN
8 20 研發部 成都 3244 歐陽鋒 程序員 3088.0 3200 NaN
9 20 研發部 成都 3251 張翠山 程序員 2056.0 4000 NaN
10 20 研發部 成都 7800 張三豐 總裁 NaN 9000 1200.0
11 20 研發部 成都 9800 駱昊 架構師 7800.0 30000 5000.0
12 20 研發部 成都 9900 王小刀 程序員 9800.0 10000 1200.0
13 20 研發部 成都 9700 王大錘 程序員 9800.0 8000 600.0
14 30 銷售部 重慶 1359 胡一刀 銷售員 3344.0 1800 200.0
15 30 銷售部 重慶 3344 黃蓉 銷售主管 7800.0 3000 800.0
16 30 銷售部 重慶 4466 苗人鳳 銷售員 3344.0 2500 NaN
merge函數的一個參數代表合並的左表、第二個參數代表合並的右表,有SQL編程經驗的同學對這兩個詞是不是感覺到非常親切。正如大家猜想的那樣,DataFrame對象的合並跟數據庫中的表連接非常類似,所以上面代碼中的how代表了合並兩張表的方式,有left、right、inner、outer四個選項;而on則代表了基於哪個列實現表的合並,相當於 SQL 表連接中的連表條件,如果左右兩表對應的列列名不同,可以用left_on和right_on參數取代on參數分別進行指定。
如果對上面的代碼稍作修改,將how參數修改為left,大家可以思考一下代碼執行的結果。
pd.merge(dept_df, all_emp_df, how='left', on='dno')
運行結果比之前的輸出多出了如下所示的一行,這是因為left代表左外連接,也就意味著左表dept_df中的數據會被完整的查出來,但是在all_emp_df中又沒有編號為40 部門的員工,所以對應的位置都被填入了空值。
17 40 運維部 天津 NaN NaN NaN NaN NaN NaN
Comments