程式碼:
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
程式碼:
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
可以透過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。
可以透過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
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