# 惯例导入2个模块
import pandas as pd
import numpy as np
# 从字典创建,后面会详述
= pd.DataFrame(dict(id=[1, 2, 3], name=["a", "b", "c"]))
df
df
id | name | |
---|---|---|
0 | 1 | a |
1 | 2 | b |
2 | 3 | c |
主要内容:
.py
是Python的源代码文件。这里采用另一种文件,更加常用于数据分析:Jupyter Notebook,.ipynb
。
Why:单元格的输出,会直接显示在单元格的下方,比较方便看。但用起来和单元格分割的.py
类似。
新建文件,扩展名改为.ipynb
即可。
常见的分工:.ipynb
写数据分析的过程;.py
保存共用的代码,作为模块在ipynb
中导入。
每个单元格可以是代码,也可以Markdown格式的文本。单元个的右下角可以选择。
“文本”的单元格,ctrl+enter可以看渲染的结果,双击可以回到编辑。
Pandas处理二维表格,其中的DataFrame对象几乎可以理解为一个Excel表:有行、列、题头等等。
DataFrame可以视为是“按列”组织的:n个列的横向并排:每个列是一个Series对象。
一个Series表示“一列”,每个Series对象又有两个部件组成,索引index和值values,其中的值values, 是一个numpy的ndarray。
反过来,一个数据序列ndarray和一个与数据等长的索引index,组成一个列Series,多个Series横向合并,组成一个DataFrame。
(index + ndarray) -> Series
(index + Series + Series + …) -> DataFrame
# 惯例导入2个模块
import pandas as pd
import numpy as np
# 从字典创建,后面会详述
= pd.DataFrame(dict(id=[1, 2, 3], name=["a", "b", "c"]))
df
df
id | name | |
---|---|---|
0 | 1 | a |
1 | 2 | b |
2 | 3 | c |
# 取某一列:得到一个Series,注意Series由index和value组成
= df["name"]
x print(x)
print(type(x))
0 a
1 b
2 c
Name: name, dtype: object
<class 'pandas.core.series.Series'>
# x的index
print(x.index)
print(type(x.index))
RangeIndex(start=0, stop=3, step=1)
<class 'pandas.core.indexes.range.RangeIndex'>
# x的value
print(x.values)
print(type(x.values)) # 是一个ndarray
['a' 'b' 'c']
<class 'numpy.ndarray'>
Series可以视为二维表格的一列。我们先用pd.Series()
函数构建
# 使用pd.Series构建一列,参数可以是一个序列结构,比如一个List
= pd.Series(["a", "b", "c", "d"])
x x
0 a
1 b
2 c
3 d
dtype: object
此时默认的index(看左侧)是从0开始的整数,并且这一列没有名称。我们可以用索引访问Series中的值。
2] # 注意2指的是index x[
'c'
# 当然也可以写入
2] = 99
x[ x
0 a
1 b
2 99
3 d
dtype: object
创建的时候可以指定index和name,name后面会成为DataFrame(二维表格,即多个列Series的横向合并)中这一列的标题。
# 创建一个简单的 Series,指定index和name
= pd.Series(
grades 85, 92, 78, 90], index=["Alice", "Bob", "Charlie", "David"], name="score"
[
)
# 打印 Series
print(grades)
# 通过索引获取数据
print("Alice的分数是:", grades["Alice"])
Alice 85
Bob 92
Charlie 78
David 90
Name: score, dtype: int64
Alice的分数是: 85
# 从ndarray或者list创建
= np.array([8, 6, 4, 2])
a print(a)
= pd.DataFrame(a)
df1 df1
[8 6 4 2]
0 | |
---|---|
0 | 8 |
1 | 6 |
2 | 4 |
3 | 2 |
= list("apple")
b print(b)
= pd.DataFrame(b, columns=["B"], index=list("abcde")) # 指定columns和index
df2 df2
['a', 'p', 'p', 'l', 'e']
B | |
---|---|
a | a |
b | p |
c | p |
d | l |
e | e |
# 从字典创建
# 字典的每一个item会成为表格的一列,key会成为列标签(列的标题),value会成为列的值
= dict(A=[1, 2, 3, 4, 5], B=list("APPLE"))
x print(x)
= pd.DataFrame(x)
df df
{'A': [1, 2, 3, 4, 5], 'B': ['A', 'P', 'P', 'L', 'E']}
A | B | |
---|---|---|
0 | 1 | A |
1 | 2 | P |
2 | 3 | P |
3 | 4 | L |
4 | 5 | E |
# 单独设置或者修改列标签(columns)
print(df.columns)
= ["C", "D"]
df.columns df
Index(['A', 'B'], dtype='object')
C | D | |
---|---|---|
0 | 1 | A |
1 | 2 | P |
2 | 3 | P |
3 | 4 | L |
4 | 5 | E |
# 单独修改行标签(index)
print(df.index)
= [5, 6, 7, 8, 9]
df.index df
RangeIndex(start=0, stop=5, step=1)
C | D | |
---|---|---|
5 | 1 | A |
6 | 2 | P |
7 | 3 | P |
8 | 4 | L |
9 | 5 | E |
一般常见的数据格式包括:
.xlsx
,2007版之前为.xls
,可以直接用Excel打开处理,不详细叙述。.csv
,也可以用Excel打开。但这种文件本质上是一个纯文本文件,和.txt
文件或者python代码.py
文件并无区别,都可以用记事本或者vscode打开。其中:
.xlsx
。对于我们大部分工作,两种保存数据的格式都没有本质的区别。
以下数据如果不做说明,都来自CSMAR(国泰安)数据库。
vscode管理工程以目录为基础,很多功能需要你打开一个目录才能生效。你在vscode中打开的目录即工作目录。
在本课程中,所有的数据,都会放在工作目录下的data文件夹。即data
文件夹现在这个.ipynb
处于目录同一层。
所用数据包括:
# 导入pandas和numpy是惯例
import pandas as pd
import numpy as np
# 读取工作目录下的data文件夹下的basic_info.xlsx文件
# 保存到df变量
= pd.read_excel("data/basic_info.xlsx") df
注意:如果read_excel出错,提示你缺少openpyxl
包(你应该可以看懂出错信息),那么按照这个包可以用以下方法二选一:
!pip install -i https://pypi.tuna.tsinghua.edu.cn/simple openpyxl
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple openpyxl
读取数据后,首先检查数据读取是否符合预期。
用.head()
方法检查前几行,.tail()
检查后几行,默认都是5行。
# 查看df的前5行 df.head()
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 |
1 | 2 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 |
2 | 4 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST |
3 | 5 | ST 星源 | 1990-02-01 | 1058536842 | 1990-12-10 | 广东省 | 深圳市 | ST |
4 | 6 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 |
再用.info()
看每一列的信息,主要看格式。
# 每一列的信息 df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 39 entries, 0 to 38
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 股票代码 39 non-null int64
1 股票简称 39 non-null object
2 公司成立日期 39 non-null object
3 注册资本 39 non-null int64
4 首次上市日期 39 non-null object
5 所属省份 39 non-null object
6 所属城市 39 non-null object
7 上市状态 39 non-null object
dtypes: int64(2), object(6)
memory usage: 2.6+ KB
我们首先观察到:
000001
。 读取时默认把股票代码
列识别为数字int64
(见info()
的结果), 因此前面的0就被去掉了。info()
结果看,数据类型Dtype
中,字符串str
会被显示为object
。 (更深入的解释见 https://stackoverflow.com/questions/21018654/strings-in-a-dataframe-but-dtype-is-object )(为什么日期数据最好是日期格式?比如你可以比较日期的先后,两个日期相减获得相差几天等等,但你无法对字符串型的日期做这类操作。)
处理这类问题一般可以 1. 在读取数据的时候就指定格式。2. 读取了数据后再转换。
pd.read_xxx()
方法,可以接收一个参数converters
,这个参数是一个字典,其中key为列名,value转换的函数。
这里我们指定股票代码
为字符串str
,用str
函数即可;公司成立日期为datetime
格式,用pd.to_datetime
函数。(这里故意漏掉首次上市日期
)
注:这里只是告诉pandas可以用什么函数来转换指定的列,而不是你自己去调用某个函数,因此只要函数名即可。
= pd.read_excel(
df "data/basic_info.xlsx", converters={"股票代码": str, "公司成立日期": pd.to_datetime}
)
df.head()
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | |
---|---|---|---|---|---|---|---|---|
0 | 000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 |
1 | 000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 |
2 | 000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST |
3 | 000005 | ST 星源 | 1990-02-01 | 1058536842 | 1990-12-10 | 广东省 | 深圳市 | ST |
4 | 000006 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 |
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 39 entries, 0 to 38
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 股票代码 39 non-null object
1 股票简称 39 non-null object
2 公司成立日期 39 non-null datetime64[ns]
3 注册资本 39 non-null int64
4 首次上市日期 39 non-null object
5 所属省份 39 non-null object
6 所属城市 39 non-null object
7 上市状态 39 non-null object
dtypes: datetime64[ns](1), int64(1), object(6)
memory usage: 2.6+ KB
查看数据的前几行以及列信息,股票代码和公司成立日期都符合我们的要求了。
读取首次上市日期
列,转换格式,再写回到同一列中。对行和列的读写是后面的内容,但逻辑还是比较简单。
"首次上市日期"] = pd.to_datetime(df["首次上市日期"])
df[ df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 39 entries, 0 to 38
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 股票代码 39 non-null object
1 股票简称 39 non-null object
2 公司成立日期 39 non-null datetime64[ns]
3 注册资本 39 non-null int64
4 首次上市日期 39 non-null datetime64[ns]
5 所属省份 39 non-null object
6 所属城市 39 non-null object
7 上市状态 39 non-null object
dtypes: datetime64[ns](2), int64(1), object(5)
memory usage: 2.6+ KB
查看info()
,数据已经符合我们的要求。
df.head()
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | |
---|---|---|---|---|---|---|---|---|
0 | 000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 |
1 | 000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 |
2 | 000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST |
3 | 000005 | ST 星源 | 1990-02-01 | 1058536842 | 1990-12-10 | 广东省 | 深圳市 | ST |
4 | 000006 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 |
对于一个DataFrame对象,每一列的标题(题头)和每一行的索引,其实都是索引index,或可称为行索引和列索引。
获得所有的列名(即获得所有的列索引):这是一个字符串类型的索引,包括每一列的列名。
df.columns
Index(['股票代码', '股票简称', '公司成立日期', '注册资本', '首次上市日期', '所属省份', '所属城市', '上市状态'], dtype='object')
获得所有的行索引:这是一个range类的索引,从0到39。如果是时间序列数据,还可以是日期格式的行索引。
df.index
RangeIndex(start=0, stop=39, step=1)
我们可以用set_index()
指定一列作为行索引,比如股票代码。
= df.set_index("股票代码")
df df.head()
股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | |
---|---|---|---|---|---|---|---|
股票代码 | |||||||
000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 |
000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 |
000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST |
000005 | ST 星源 | 1990-02-01 | 1058536842 | 1990-12-10 | 广东省 | 深圳市 | ST |
000006 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 |
注意:pandas中,大部分“写”操作(修改某个东西),默认都“不会改变原值”:你会获得一个包括修改后的新数据的DataFrame。 因此,如果要改变原值,可以把新数据重新赋值给原来的变量名(如上个cell),也可以加入参数inplace = True
,如
# 仅作示范,不要执行,因为经过上个cell之后,已经没有`股票代码`这一列了
# df.set_index('股票代码',inplace = True)
再看df.index
,此时索引已经变成我们指定列,
df.index
Index(['000001', '000002', '000004', '000005', '000006', '000007', '000008',
'000009', '000010', '000011', '000012', '000014', '000016', '000017',
'000019', '000020', '000021', '000023', '000025', '000026', '000027',
'000028', '000029', '000030', '000031', '000032', '000034', '000035',
'000036', '000037', '000038', '000039', '000040', '000042', '000045',
'000046', '000048', '000049', '000050'],
dtype='object', name='股票代码')
重命名列标签是可以常用操作(改列名),一般有两种方式:
.rename()
方法:这个方法接受一个字典参数columns
,其中key是旧名,value是新名。(从旧到新的映射)df.columns
整体重新赋值。注意,这个方法会直接修改原数据。例如:把“首次上市日期”改为“IPO_DATE”:
# 方法1:rename()
={"首次上市日期": "IPO_DATE"}).head()
df.rename(columns# 这里不使用inplace = True,因此会返回一个新的数据,不会改写原数据。
股票简称 | 公司成立日期 | 注册资本 | IPO_DATE | 所属省份 | 所属城市 | 上市状态 | |
---|---|---|---|---|---|---|---|
股票代码 | |||||||
000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 |
000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 |
000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST |
000005 | ST 星源 | 1990-02-01 | 1058536842 | 1990-12-10 | 广东省 | 深圳市 | ST |
000006 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 |
# 方法2:对columns重新赋值
= df.copy() # 这个方法会直接修改原值。为了保持原数据不变,在一个副本上演示。
df2
= list("ABCDEFG") # 赋值一个同样长度的list。
df2.columns # df2的列名直接被改变了。 df2.head()
A | B | C | D | E | F | G | |
---|---|---|---|---|---|---|---|
股票代码 | |||||||
000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 |
000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 |
000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST |
000005 | ST 星源 | 1990-02-01 | 1058536842 | 1990-12-10 | 广东省 | 深圳市 | ST |
000006 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 |
选择行和列的一万种方法。
选1列,[列名]
,返回一个Series
对象。
= df["股票简称"]
sec_name sec_name.head()
股票代码
000001 平安银行
000002 万科A
000004 国华网安
000005 ST 星源
000006 深振业A
Name: 股票简称, dtype: object
查看列的类型
type(sec_name) # pandas.core.series.Series
pandas.core.series.Series
选择多列,[[列名的List]]
,返回一个DataFrame
对象。
"股票简称", "上市状态"]].head() df[[
股票简称 | 上市状态 | |
---|---|---|
股票代码 | ||
000001 | 平安银行 | 正常上市 |
000002 | 万科A | 正常上市 |
000004 | 国华网安 | ST |
000005 | ST 星源 | ST |
000006 | 深振业A | 正常上市 |
.loc[行索引,列索引]
可以按索引选择行或者列(行的索引是index,列的索引是columns)
"000001", "000002"], ["股票简称", "上市状态"]] df.loc[[
股票简称 | 上市状态 | |
---|---|---|
股票代码 | ||
000001 | 平安银行 | 正常上市 |
000002 | 万科A | 正常上市 |
也可以只取一个值:
"000001", "股票简称"] df.loc[
'平安银行'
行索引和列索引位置,都可以用冒号:
表示起点和终点(注:起点终点都包括,和Python的List切片不同!)。
如果只有:
,则代表所有行或者列。
比如:获取行索引为000001
到000005
,列索引为股票简称
到注册资本
之间的所有数据。
"000001":"000005", "股票简称":"注册资本"] df.loc[
股票简称 | 公司成立日期 | 注册资本 | |
---|---|---|---|
股票代码 | |||
000001 | 平安银行 | 1987-12-22 | 19405918198 |
000002 | 万科A | 1988-11-01 | 11625383375 |
000004 | 国华网安 | 1986-05-05 | 156003000 |
000005 | ST 星源 | 1990-02-01 | 1058536842 |
获取“所有行(所有股票)的股票简称和上市状况”。但太长,只查看前几行
= df.loc[:, ["股票简称", "上市状态"]]
x x.head()
股票简称 | 上市状态 | |
---|---|---|
股票代码 | ||
000001 | 平安银行 | 正常上市 |
000002 | 万科A | 正常上市 |
000004 | 国华网安 | ST |
000005 | ST 星源 | ST |
000006 | 深振业A | 正常上市 |
当然,如果只是取某列的全部行,上面的代码就和df[['股票简称','上市状态']]
等价。
同Python的切片,冒号一侧不写,则表示“到尽头”。
= df.loc[:"000002", "所属城市":]
x x
所属城市 | 上市状态 | |
---|---|---|
股票代码 | ||
000001 | 深圳市 | 正常上市 |
000002 | 深圳市 | 正常上市 |
在选择列的操作中,列的次序和你提供的列名List的次序完全一样。因此,取列的操作,包括[]
和.loc[]
,都可以用于改变列的次序。
例如,把列名逆序:(当然,只要你获得了列名的List,就可以任意排序)
# 逆序列名
= df.columns # 取得列名
cols
df[-1]
cols[::# 对逆序后的列名,用`[]`取列。当然,用df.loc[:,cols[::-1]]也一样。 ].head()
上市状态 | 所属城市 | 所属省份 | 首次上市日期 | 注册资本 | 公司成立日期 | 股票简称 | |
---|---|---|---|---|---|---|---|
股票代码 | |||||||
000001 | 正常上市 | 深圳市 | 广东省 | 1991-04-03 | 19405918198 | 1987-12-22 | 平安银行 |
000002 | 正常上市 | 深圳市 | 广东省 | 1991-01-29 | 11625383375 | 1988-11-01 | 万科A |
000004 | ST | 深圳市 | 广东省 | 1991-01-14 | 156003000 | 1986-05-05 | 国华网安 |
000005 | ST | 深圳市 | 广东省 | 1990-12-10 | 1058536842 | 1990-02-01 | ST 星源 |
000006 | 正常上市 | 深圳市 | 广东省 | 1992-04-27 | 1349995046 | 1989-04-01 | 深振业A |
每一列之间可以很方便地进行运算:任何操作都会自动应用到每一列的所有元素(如同NumPy中的广播)
例如,注册资本转换为亿元,并保留2位数字。
"注册资本"] / 100000000).round(2).head() (df[
股票代码
000001 194.06
000002 116.25
000004 1.56
000005 10.59
000006 13.50
Name: 注册资本, dtype: float64
注意到,Series经过运算后也会得到一个Series,可以加入原数据中的一个列:直接=赋值即可。
"注册资本_亿"] = (df["注册资本"] / 100000000).round(2)
df[ df.head()
股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|
股票代码 | ||||||||
000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 | 194.06 |
000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST | 1.56 |
000005 | ST 星源 | 1990-02-01 | 1058536842 | 1990-12-10 | 广东省 | 深圳市 | ST | 10.59 |
000006 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 | 13.50 |
使用df.drop(行或者列标签,axis=0|1)
删除行或者列,其中参数axis = 0为行,=1为列。
# 删除“注册资本”列;如果要修改df本身,则inplace = True
"注册资本", axis=1).head() df.drop(
股票简称 | 公司成立日期 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|
股票代码 | |||||||
000001 | 平安银行 | 1987-12-22 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 | 194.06 |
000002 | 万科A | 1988-11-01 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
000004 | 国华网安 | 1986-05-05 | 1991-01-14 | 广东省 | 深圳市 | ST | 1.56 |
000005 | ST 星源 | 1990-02-01 | 1990-12-10 | 广东省 | 深圳市 | ST | 10.59 |
000006 | 深振业A | 1989-04-01 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 | 13.50 |
# 删除标签为00001的行
"000001", axis=0).head() df.drop(
股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|
股票代码 | ||||||||
000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST | 1.56 |
000005 | ST 星源 | 1990-02-01 | 1058536842 | 1990-12-10 | 广东省 | 深圳市 | ST | 10.59 |
000006 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 | 13.50 |
000007 | *ST 全新 | 1988-11-21 | 346448044 | 1992-04-13 | 广东省 | 深圳市 | 正常上市 | 3.46 |
注意: pandas中,包括drop在内,很多操作“默认不修改原数据”,而是“修改后的数据作为返回值”。因此,需要这样把修改后的数据再次赋值。
df = df.drop( <参数> ) # 把修改后的数据再次赋值给df
如果要“原地修改”,直接改变原始数据,那么需要添加 inplace=True
,此时原值被修改,返回值变为None。
df.drop( <参数>, inplace=True ) # 在原始数据上修改,不用再赋值
.iloc[行坐标,列坐标]
:用法和loc
非常类似,但只是把索引index(索引可以是任何序列),换成下标(从0开始)。
用法和Python的切片很类似。
# 首先把股票代码还原成普通的列
=True)
df.reset_index(inplace df.head()
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
0 | 000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 | 194.06 |
1 | 000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
2 | 000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST | 1.56 |
3 | 000005 | ST 星源 | 1990-02-01 | 1058536842 | 1990-12-10 | 广东省 | 深圳市 | ST | 10.59 |
4 | 000006 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 | 13.50 |
# 取前5行,近似于df.head()
5] df.iloc[:
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
0 | 000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 | 194.06 |
1 | 000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
2 | 000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST | 1.56 |
3 | 000005 | ST 星源 | 1990-02-01 | 1058536842 | 1990-12-10 | 广东省 | 深圳市 | ST | 10.59 |
4 | 000006 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 | 13.50 |
# iloc接受列表,如取0,2,4,6行
0, 2, 4, 6]] df.iloc[[
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
0 | 000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 | 194.06 |
2 | 000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST | 1.56 |
4 | 000006 | 深振业A | 1989-04-01 | 1349995046 | 1992-04-27 | 广东省 | 深圳市 | 正常上市 | 13.50 |
6 | 000008 | 神州高铁 | 1989-10-11 | 2780795346 | 1992-05-07 | 北京市 | 北京市 | 正常上市 | 27.81 |
# 抽样:随机选行。
5) df.sample(
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
19 | 000026 | 飞亚达 | 1993-04-18 | 426051015 | 1993-06-03 | 广东省 | 深圳市 | 正常上市 | 4.26 |
38 | 000050 | 深天马A | 1983-11-08 | 2457747661 | 1995-03-15 | 广东省 | 深圳市 | 正常上市 | 24.58 |
31 | 000039 | 中集集团 | 1992-09-30 | 3595014000 | 1994-03-23 | 广东省 | 深圳市 | 正常上市 | 35.95 |
32 | 000040 | 东旭蓝天 | 1994-06-15 | 1486873870 | 1994-08-08 | 广东省 | 深圳市 | 正常上市 | 14.87 |
15 | 000020 | 深华发A | 1992-03-20 | 283161227 | 1992-04-28 | 广东省 | 深圳市 | 正常上市 | 2.83 |
# 按条件筛选:如,选择注册资本大于20亿的公司
# 创建条件
= df["注册资本"] > 2000000000
mask mask.head()
0 True
1 True
2 False
3 False
4 False
Name: 注册资本, dtype: bool
df[mask]
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
0 | 000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 | 194.06 |
1 | 000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
6 | 000008 | 神州高铁 | 1989-10-11 | 2780795346 | 1992-05-07 | 北京市 | 北京市 | 正常上市 | 27.81 |
7 | 000009 | 中国宝安 | 1990-09-01 | 2579213965 | 1991-06-25 | 广东省 | 深圳市 | 正常上市 | 25.79 |
10 | 000012 | 南玻A | 1984-09-10 | 3070692107 | 1992-02-28 | 广东省 | 深圳市 | 正常上市 | 30.71 |
12 | 000016 | 深康佳A | 1980-10-01 | 2407945408 | 1992-03-27 | 广东省 | 深圳市 | 正常上市 | 24.08 |
20 | 000027 | 深圳能源 | 1993-06-02 | 4757389916 | 1993-09-03 | 广东省 | 深圳市 | 正常上市 | 47.57 |
24 | 000031 | 大悦城 | 1993-09-26 | 4286313339 | 1993-10-08 | 广东省 | 深圳市 | 正常上市 | 42.86 |
27 | 000035 | 中国天楹 | 1994-01-08 | 2523777297 | 1994-04-08 | 江苏省 | 南通市 | 正常上市 | 25.24 |
31 | 000039 | 中集集团 | 1992-09-30 | 3595014000 | 1994-03-23 | 广东省 | 深圳市 | 正常上市 | 35.95 |
35 | 000046 | 泛海控股 | 1989-01-04 | 5196200656 | 1994-09-12 | 北京市 | 北京市 | 正常上市 | 51.96 |
38 | 000050 | 深天马A | 1983-11-08 | 2457747661 | 1995-03-15 | 广东省 | 深圳市 | 正常上市 | 24.58 |
# 用loc操作也可以
df.loc[mask]
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
0 | 000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 | 194.06 |
1 | 000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
6 | 000008 | 神州高铁 | 1989-10-11 | 2780795346 | 1992-05-07 | 北京市 | 北京市 | 正常上市 | 27.81 |
7 | 000009 | 中国宝安 | 1990-09-01 | 2579213965 | 1991-06-25 | 广东省 | 深圳市 | 正常上市 | 25.79 |
10 | 000012 | 南玻A | 1984-09-10 | 3070692107 | 1992-02-28 | 广东省 | 深圳市 | 正常上市 | 30.71 |
12 | 000016 | 深康佳A | 1980-10-01 | 2407945408 | 1992-03-27 | 广东省 | 深圳市 | 正常上市 | 24.08 |
20 | 000027 | 深圳能源 | 1993-06-02 | 4757389916 | 1993-09-03 | 广东省 | 深圳市 | 正常上市 | 47.57 |
24 | 000031 | 大悦城 | 1993-09-26 | 4286313339 | 1993-10-08 | 广东省 | 深圳市 | 正常上市 | 42.86 |
27 | 000035 | 中国天楹 | 1994-01-08 | 2523777297 | 1994-04-08 | 江苏省 | 南通市 | 正常上市 | 25.24 |
31 | 000039 | 中集集团 | 1992-09-30 | 3595014000 | 1994-03-23 | 广东省 | 深圳市 | 正常上市 | 35.95 |
35 | 000046 | 泛海控股 | 1989-01-04 | 5196200656 | 1994-09-12 | 北京市 | 北京市 | 正常上市 | 51.96 |
38 | 000050 | 深天马A | 1983-11-08 | 2457747661 | 1995-03-15 | 广东省 | 深圳市 | 正常上市 | 24.58 |
# 复合条件:选择注册资本>20亿,且所属省份位广东省的公司
= df["注册资本"] > 2000000000 # 数字列,可以直接运算(包括算数运算和条件运算)
cond1 = df["所属省份"].str.contains(
cond2 "广东"
# 字符串列,需要调用字符串方法`.str.某函数()`,
)
# Pandas字符串方法见https://pandas.pydata.org/pandas-docs/stable/user_guide/text.html
# 常用的如包含.str.contains(),以什么开头.str.startswith(),以什么结尾.str.endswith()
# 方法1,用np.logical_xxx()函数,构造一个新的布尔型序列
= np.logical_and(cond1, cond2)
mask df[mask]
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
0 | 000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 | 194.06 |
1 | 000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
7 | 000009 | 中国宝安 | 1990-09-01 | 2579213965 | 1991-06-25 | 广东省 | 深圳市 | 正常上市 | 25.79 |
10 | 000012 | 南玻A | 1984-09-10 | 3070692107 | 1992-02-28 | 广东省 | 深圳市 | 正常上市 | 30.71 |
12 | 000016 | 深康佳A | 1980-10-01 | 2407945408 | 1992-03-27 | 广东省 | 深圳市 | 正常上市 | 24.08 |
20 | 000027 | 深圳能源 | 1993-06-02 | 4757389916 | 1993-09-03 | 广东省 | 深圳市 | 正常上市 | 47.57 |
24 | 000031 | 大悦城 | 1993-09-26 | 4286313339 | 1993-10-08 | 广东省 | 深圳市 | 正常上市 | 42.86 |
31 | 000039 | 中集集团 | 1992-09-30 | 3595014000 | 1994-03-23 | 广东省 | 深圳市 | 正常上市 | 35.95 |
38 | 000050 | 深天马A | 1983-11-08 | 2457747661 | 1995-03-15 | 广东省 | 深圳市 | 正常上市 | 24.58 |
# 方法2,直接在DataFrame中选,Pandas的[]操作,接受"与&, 或|,非~"操作
& cond2] df[cond1
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
0 | 000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 | 194.06 |
1 | 000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
7 | 000009 | 中国宝安 | 1990-09-01 | 2579213965 | 1991-06-25 | 广东省 | 深圳市 | 正常上市 | 25.79 |
10 | 000012 | 南玻A | 1984-09-10 | 3070692107 | 1992-02-28 | 广东省 | 深圳市 | 正常上市 | 30.71 |
12 | 000016 | 深康佳A | 1980-10-01 | 2407945408 | 1992-03-27 | 广东省 | 深圳市 | 正常上市 | 24.08 |
20 | 000027 | 深圳能源 | 1993-06-02 | 4757389916 | 1993-09-03 | 广东省 | 深圳市 | 正常上市 | 47.57 |
24 | 000031 | 大悦城 | 1993-09-26 | 4286313339 | 1993-10-08 | 广东省 | 深圳市 | 正常上市 | 42.86 |
31 | 000039 | 中集集团 | 1992-09-30 | 3595014000 | 1994-03-23 | 广东省 | 深圳市 | 正常上市 | 35.95 |
38 | 000050 | 深天马A | 1983-11-08 | 2457747661 | 1995-03-15 | 广东省 | 深圳市 | 正常上市 | 24.58 |
# 稍微复杂一点,注册资本大于20亿,但不在广东省
& (~cond2)] df[cond1
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
6 | 000008 | 神州高铁 | 1989-10-11 | 2780795346 | 1992-05-07 | 北京市 | 北京市 | 正常上市 | 27.81 |
27 | 000035 | 中国天楹 | 1994-01-08 | 2523777297 | 1994-04-08 | 江苏省 | 南通市 | 正常上市 | 25.24 |
35 | 000046 | 泛海控股 | 1989-01-04 | 5196200656 | 1994-09-12 | 北京市 | 北京市 | 正常上市 | 51.96 |
# 时间方法: '.dt.某函数()'或者'.dt.某属性'
# 更多时间方法,见https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html
# 常见如取得年月日:.dt.year, .dt.month,.dt.day,取得星期几.dt.weekday等等
# 如:选择91年下半年上市的公司
= df["首次上市日期"].dt.year == 1991
cond1 = df["首次上市日期"].dt.month >= 6
cond2 & cond2] df[cond1
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
7 | 000009 | 中国宝安 | 1990-09-01 | 2579213965 | 1991-06-25 | 广东省 | 深圳市 | 正常上市 | 25.79 |
# 查询函数query(),可以把几个条件写成一个查询字符串
# 注意引号的嵌套:字符串必须有引号,如'万科A',查询语句本身也是字符串,
# 因此外层可以用不同的引号。这里内层用单引号,外层用双引号。
" 股票简称 == '万科A' ") df.query(
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
1 | 000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
# 查询语句也可以用复合(& | ~)条件
# 并且接受变量作为条件,只要在查询字符串中加入`@变量名` 即可
# 上市年份 = a_year,且省份不是广东省
= 1992
a_year
df.query("首次上市日期.dt.year == @a_year & ~( 所属省份 == '广东省')"
# 用"所属省份 != '广东省'"也可 )
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
6 | 000008 | 神州高铁 | 1989-10-11 | 2780795346 | 1992-05-07 | 北京市 | 北京市 | 正常上市 | 27.81 |
df.sort_values( [<列名>] )
可以按某一列排序,多个列名可以使用List。参数ascending=True
为从小到大排序,默认为True
。
类似的,df.sort_index
可以按索引排序。
# 按注册资本,逆序排序,看最前面(最大)5个
"注册资本", ascending=False).head() df.sort_values(
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
0 | 000001 | 平安银行 | 1987-12-22 | 19405918198 | 1991-04-03 | 广东省 | 深圳市 | 正常上市 | 194.06 |
1 | 000002 | 万科A | 1988-11-01 | 11625383375 | 1991-01-29 | 广东省 | 深圳市 | 正常上市 | 116.25 |
35 | 000046 | 泛海控股 | 1989-01-04 | 5196200656 | 1994-09-12 | 北京市 | 北京市 | 正常上市 | 51.96 |
20 | 000027 | 深圳能源 | 1993-06-02 | 4757389916 | 1993-09-03 | 广东省 | 深圳市 | 正常上市 | 47.57 |
24 | 000031 | 大悦城 | 1993-09-26 | 4286313339 | 1993-10-08 | 广东省 | 深圳市 | 正常上市 | 42.86 |
# 先按省份排序,同省份内,按注册资本排序
"所属省份", "注册资本_亿"]).head(10) df.sort_values([
股票代码 | 股票简称 | 公司成立日期 | 注册资本 | 首次上市日期 | 所属省份 | 所属城市 | 上市状态 | 注册资本_亿 | |
---|---|---|---|---|---|---|---|---|---|
6 | 000008 | 神州高铁 | 1989-10-11 | 2780795346 | 1992-05-07 | 北京市 | 北京市 | 正常上市 | 27.81 |
35 | 000046 | 泛海控股 | 1989-01-04 | 5196200656 | 1994-09-12 | 北京市 | 北京市 | 正常上市 | 51.96 |
23 | 000030 | 富奥股份 | 1993-08-28 | 1810552111 | 1993-09-29 | 吉林省 | 长春市 | 正常上市 | 18.11 |
17 | 000023 | 深天地A | 1984-09-18 | 138756240 | 1993-04-29 | 广东省 | 深圳市 | 正常上市 | 1.39 |
2 | 000004 | 国华网安 | 1986-05-05 | 156003000 | 1991-01-14 | 广东省 | 深圳市 | ST | 1.56 |
11 | 000014 | 沙河股份 | 1992-04-21 | 201705187 | 1992-06-02 | 广东省 | 深圳市 | 正常上市 | 2.02 |
15 | 000020 | 深华发A | 1992-03-20 | 283161227 | 1992-04-28 | 广东省 | 深圳市 | 正常上市 | 2.83 |
37 | 000049 | 德赛电池 | 1995-02-18 | 300298970 | 1995-03-20 | 广东省 | 深圳市 | 正常上市 | 3.00 |
5 | 000007 | *ST 全新 | 1988-11-21 | 346448044 | 1992-04-13 | 广东省 | 深圳市 | 正常上市 | 3.46 |
19 | 000026 | 飞亚达 | 1993-04-18 | 426051015 | 1993-06-03 | 广东省 | 深圳市 | 正常上市 | 4.26 |
继续利用上述basic_info.xlsx
数据,建立一个新的ipynb文件(完整学号-姓名-行列基本操作练习.ipynb),完成以下练习。(题目如果要求打印DF,都只打印前5行)
练习1:基础操作
从 DataFrame df
中选择 股票代码
和 股票简称
两列,并保存到变量 selected_columns
。然后选择 股票代码
为 000002
的行,并仅显示 公司成立日期
和 注册资本
两列,保存到变量 selected_row
。打印 selected_columns
和 selected_row
。
练习2:列运算与删除列
在 df
中添加一个新列 注册资本(亿)
,其值为 注册资本
列的值除以 10^8,然后删除 注册资本
列。保存修改后的 DataFrame到变量 df_modified
,并打印前5行。
练习3:按条件筛选与排序
筛选出 所属省份
为 广东省
且 上市状态
为 正常上市
的所有行,并按 公司成立日期
降序排列。将结果保存到变量 sorted_df
,并打印 sorted_df
。
练习4:复合条件筛选与字符串方法
筛选出 公司成立日期
在 1990年
之前且 股票简称
包含 A
字符的所有行,并创建一个新列 成立年份
,其值为 公司成立日期
的年份。将结果保存到变量 filtered_df
,并打印 filtered_df
。
练习5:复杂逻辑判断和后续操作
将 首次上市日期
转换为 datetime
格式。筛选出 首次上市日期
早于 1992-01-01
的公司,并判断这些公司的 注册资本(亿)
是否大于 100 亿元。如果大于 100 亿元,则标记为 “大公司”,否则标记为 “小公司”。将这个标记作为新列 公司规模
加入 DataFrame。将结果保存到变量 final_df
,并打印 final_df
。
构造一个示例数据
import pandas as pd
import numpy as np
# np.arange(8) + 1 生成1-8的数组
# np.random.randn(8) 生成标准正态分布随机数
= pd.DataFrame(dict(A=np.arange(8) + 1, B=np.random.randn(8)))
df df
A | B | |
---|---|---|
0 | 1 | 1.074429 |
1 | 2 | -1.244722 |
2 | 3 | 0.874934 |
3 | 4 | -0.881983 |
4 | 5 | 0.011209 |
5 | 6 | -0.577859 |
6 | 7 | -0.246587 |
7 | 8 | 0.317394 |
可以(并且推荐)采用.loc[ <行> , <列>]
,可以引用指定列上的指定列的值。
例如:把负数全部改为0
= df.B < 0 # 一个bool序列
mask "B"] = 0 # 简写成 df.loc[df.B < 0 , 'B']也可以
df.loc[mask, df
A | B | |
---|---|---|
0 | 1 | 1.074429 |
1 | 2 | 0.000000 |
2 | 3 | 0.874934 |
3 | 4 | 0.000000 |
4 | 5 | 0.011209 |
5 | 6 | 0.000000 |
6 | 7 | 0.000000 |
7 | 8 | 0.317394 |
如果新数据在另一个列表、ndarray或者Series中,向要覆盖到原数据的指定位置, 同样采用loc[]
。
# 新数据在另一个array或者Series中
= np.array([97, 98, 99])
new_data
# 要替换的值,在index的0,3,6号
= df.index % 3 == 0
mask # 0,3,6号是True
# 把new_data覆盖到df的B列的指定位置
"B"] = new_data
df.loc[mask,
df
A | B | |
---|---|---|
0 | 1 | 97.000000 |
1 | 2 | 0.000000 |
2 | 3 | 0.874934 |
3 | 4 | 98.000000 |
4 | 5 | 0.011209 |
5 | 6 | 0.000000 |
6 | 7 | 99.000000 |
7 | 8 | 0.317394 |
还是那个问题:Pandas也要区分副本和视图。
loc[]
,你对此进行赋值,将会修改原数据。.query()
。你对此赋值,不会改变原值。但问题在于:这两种操作的区分往往不明显,因此直接修改DF数据时,最好复查。
= pd.DataFrame(dict(A=np.arange(8) + 1, B=np.random.randn(8)))
df df
A | B | |
---|---|---|
0 | 1 | -0.043541 |
1 | 2 | 0.758782 |
2 | 3 | 0.204050 |
3 | 4 | 0.771142 |
4 | 5 | -0.028145 |
5 | 6 | -0.537927 |
6 | 7 | 0.107722 |
7 | 8 | -0.700706 |
= df.copy() # 把df拷贝一份
df1
# 选择B > 0 的样本,并且把他们的A属性改为99
# `loc[]`生成一个视图,直接赋值可以正常修改原值
> 0, "A"] = 99
df1.loc[df1.B df1
A | B | |
---|---|---|
0 | 1 | -0.043541 |
1 | 99 | 0.758782 |
2 | 99 | 0.204050 |
3 | 99 | 0.771142 |
4 | 5 | -0.028145 |
5 | 6 | -0.537927 |
6 | 99 | 0.107722 |
7 | 8 | -0.700706 |
= df.copy()
df2
# 链式操作:多次取行或列,串联在一起,最终赋值
# 先选择B>0的行,在选择A列,赋值
> 0]["A"] = 99
df2.loc[df2.B
# 修改原值失败!并且弹出警告
# A value is trying to be set on a copy of a slice from a DataFrame.
df2
/tmp/ipykernel_3555889/2321145153.py:5: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
df2.loc[df2.B > 0]["A"] = 99
A | B | |
---|---|---|
0 | 1 | -0.043541 |
1 | 2 | 0.758782 |
2 | 3 | 0.204050 |
3 | 4 | 0.771142 |
4 | 5 | -0.028145 |
5 | 6 | -0.537927 |
6 | 7 | 0.107722 |
7 | 8 | -0.700706 |
# 失败但无警告
= df.copy()
df3
# 这是个隐蔽的链式操作!
# 等价于 df3[['A','B']].loc[df3.B>0,'A'] = 999
= df3[["A", "B"]]
x > 0, "A"] = 999
x.loc[df3.B df3
A | B | |
---|---|---|
0 | 1 | -0.043541 |
1 | 2 | 0.758782 |
2 | 3 | 0.204050 |
3 | 4 | 0.771142 |
4 | 5 | -0.028145 |
5 | 6 | -0.537927 |
6 | 7 | 0.107722 |
7 | 8 | -0.700706 |
利用以下虚拟数据,在Passed列中,把合格的同学标记为True,不合格的同学标记为False,并打印数据。
import pandas as pd
# 创建数据集
= {
data "Name": [
"Alice",
"Bob",
"Charlie",
"David",
"Eve",
"Frank",
"Grace",
"Hannah",
"Isaac",
"Jack",
],"Score": [88, 58, 65, 95, 78, 55, 90, 87, 52, 91],
"Passed": [False] * 10,
}= pd.DataFrame(data)
df df
Name | Score | Passed | |
---|---|---|---|
0 | Alice | 88 | False |
1 | Bob | 58 | False |
2 | Charlie | 65 | False |
3 | David | 95 | False |
4 | Eve | 78 | False |
5 | Frank | 55 | False |
6 | Grace | 90 | False |
7 | Hannah | 87 | False |
8 | Isaac | 52 | False |
9 | Jack | 91 | False |
Pandas使用依然使用np.nan
来表示缺失值。
构造一个带有缺失值的Series对象(一个列):
= pd.Series(["a", "b", np.nan, "d", np.nan]) # 创建一个Series对象(一列)
df print(df)
0 a
1 b
2 NaN
3 d
4 NaN
dtype: object
常用的NA方法有:
isnull()
: 判断什么值为缺失值notnull()
: 与上一个方法相反。dropna()
:去除缺失值fillna()
: 按一定的规则填充缺失值# 使用Series对象自带的isnull()方法 df.isnull()
0 False
1 False
2 True
3 False
4 True
dtype: bool
问:这一列是否有NA,或者有多少个?对上述结果求和即可!True会被是为1,False被视为0
sum() # 2个缺失值 df.isnull().
2
使用dropna
可以返回所有非NA的值:
# 注意,要修改df本身,需要用参数inplace = True, df.dropna()
0 a
1 b
3 d
dtype: object
与下面的代码是等价的。
# 先获得非na值的布尔序列,再筛选。 df[df.notna()]
0 a
1 b
3 d
dtype: object
但是对于DataFrame对象(二维表格,多个列的横向合并),情况稍微复杂一点:不同的列的缺失值可能在不同的位置。
= pd.DataFrame(
df dict(A=[1, 2, np.nan, np.nan], B=[4, np.nan, np.nan, 7], C=[7, np.nan, np.nan, 0])
) df
A | B | C | |
---|---|---|---|
0 | 1.0 | 4.0 | 7.0 |
1 | 2.0 | NaN | NaN |
2 | NaN | NaN | NaN |
3 | NaN | 7.0 | 0.0 |
dropna()
默认会去掉包括“任何”缺失值的行:只要有缺失值,这一行就会被去掉。
df.dropna()
A | B | C | |
---|---|---|---|
0 | 1.0 | 4.0 | 7.0 |
参数how='all'
只会去掉所有值都是NA的行。
="all") df.dropna(how
A | B | C | |
---|---|---|---|
0 | 1.0 | 4.0 | 7.0 |
1 | 2.0 | NaN | NaN |
3 | NaN | 7.0 | 0.0 |
参数axis = 0|1
指示对行还是列操作(默认是行),所以要对列操作,只要加入axis = 1
"D"] = np.nan # 加一列全NA列
df[ df
A | B | C | D | |
---|---|---|---|---|
0 | 1.0 | 4.0 | 7.0 | NaN |
1 | 2.0 | NaN | NaN | NaN |
2 | NaN | NaN | NaN | NaN |
3 | NaN | 7.0 | 0.0 | NaN |
="all", axis=1) # 去掉所有值都为NA的列。 df.dropna(how
A | B | C | |
---|---|---|---|
0 | 1.0 | 4.0 | 7.0 |
1 | 2.0 | NaN | NaN |
2 | NaN | NaN | NaN |
3 | NaN | 7.0 | 0.0 |
如果需要填充缺失值,而不是去除,可以使用fillna()
。
# 创建随机数df,
# 利用np.random.randn()创建7行3列的标准正态分布随机数,转为DataFrame
= pd.DataFrame(np.random.randn(7, 3))
df = ["A", "B", "C"]
df.columns 2:5, 1] = np.nan
df.iloc[2, 2] = np.nan
df.iloc[: df
A | B | C | |
---|---|---|---|
0 | -0.293811 | -1.708470 | NaN |
1 | -0.754209 | 2.221121 | NaN |
2 | -0.632106 | NaN | 0.581108 |
3 | -1.070064 | NaN | 0.011758 |
4 | 0.276283 | NaN | 0.025015 |
5 | 1.011524 | -1.273671 | 0.989935 |
6 | -0.587565 | 0.716118 | -0.086710 |
0) # 用0填充缺失值 df.fillna(
A | B | C | |
---|---|---|---|
0 | -0.293811 | -1.708470 | 0.000000 |
1 | -0.754209 | 2.221121 | 0.000000 |
2 | -0.632106 | 0.000000 | 0.581108 |
3 | -1.070064 | 0.000000 | 0.011758 |
4 | 0.276283 | 0.000000 | 0.025015 |
5 | 1.011524 | -1.273671 | 0.989935 |
6 | -0.587565 | 0.716118 | -0.086710 |
# 用前值填充。如果第一个值就是NA就无法填充了。
# df.fillna(method="ffill") # 老版本Pandas的方法
# 新版的方法 df.ffill()
A | B | C | |
---|---|---|---|
0 | -0.293811 | -1.708470 | NaN |
1 | -0.754209 | 2.221121 | NaN |
2 | -0.632106 | 2.221121 | 0.581108 |
3 | -1.070064 | 2.221121 | 0.011758 |
4 | 0.276283 | 2.221121 | 0.025015 |
5 | 1.011524 | -1.273671 | 0.989935 |
6 | -0.587565 | 0.716118 | -0.086710 |
# 用后值填充。
# df.fillna(method="bfill") # 老版本Pandas的方法
# 新版的方法 df.bfill()
A | B | C | |
---|---|---|---|
0 | -0.293811 | -1.708470 | 0.581108 |
1 | -0.754209 | 2.221121 | 0.581108 |
2 | -0.632106 | -1.273671 | 0.581108 |
3 | -1.070064 | -1.273671 | 0.011758 |
4 | 0.276283 | -1.273671 | 0.025015 |
5 | 1.011524 | -1.273671 | 0.989935 |
6 | -0.587565 | 0.716118 | -0.086710 |
还可以用均值填充,这样填充后不改变均值
# 还可以填充均值或者中位数
=True) # 用均值填充
df.B.fillna(df.B.mean(), inplace=True) # 用中位数填充 df.C.fillna(df.C.median(), inplace
= pd.DataFrame(dict(A=["a", "b", "c"] * 2, B=[1, 2, 3] * 2))
df df
A | B | |
---|---|---|
0 | a | 1 |
1 | b | 2 |
2 | c | 3 |
3 | a | 1 |
4 | b | 2 |
5 | c | 3 |
# 判断这一整行是否前面出现过(默认以首次出现为基准) df.duplicated()
0 False
1 False
2 False
3 True
4 True
5 True
dtype: bool
# 去除重复行
df.drop_duplicates() # 下面代码等价
# df[~df.duplicated()] # 获得重复行,取反作为筛选条件
A | B | |
---|---|---|
0 | a | 1 |
1 | b | 2 |
2 | c | 3 |
= np.random.uniform(30, 100, 6).round(1)
scores = pd.DataFrame(dict(name=list("陈李张王周吴"), score=scores))
df df
name | score | |
---|---|---|
0 | 陈 | 86.1 |
1 | 李 | 32.2 |
2 | 张 | 36.3 |
3 | 王 | 36.4 |
4 | 周 | 68.4 |
5 | 吴 | 41.4 |
例如,把score列转为rank(ABCDE)。
利用map()函数,你提供一个转换器(字典),可以把ii某一列所有的值,转为 dadf
= df.score // 10 # 分数整除(获得十位)
score10x # 这是一个Series score10x
0 8.0
1 3.0
2 3.0
3 3.0
4 6.0
5 4.0
Name: score, dtype: float64
= {10: "A", 9: "A", 8: "B", 7: "C", 6: "D"} # 不同的分数段,对应的评级
score_to_rank = score10x.map(score_to_rank) # 利用map()函数,把分数的10位映射为rank
rank rank
0 B
1 NaN
2 NaN
3 NaN
4 D
5 NaN
Name: score, dtype: object
"rank"] = rank.fillna("E") # 低于60不在字典中,会映射为NA,填充'E'即可
df[ df
name | score | rank | |
---|---|---|---|
0 | 陈 | 86.1 | B |
1 | 李 | 32.2 | E |
2 | 张 | 36.3 | E |
3 | 王 | 36.4 | E |
4 | 周 | 68.4 | D |
5 | 吴 | 41.4 | E |
除了提供一个字典,也可以提供一个函数。显然函数的灵活性会高一点,比如可以写函数说明,参数检查,简单测试等等。
def calc_rank(x):
"""根据分数0~100,计算评级rank,返回字符串ABC等"""
# 参数的定义域检测等略
= {
score_to_rank 10: "A",
9: "A",
8: "B",
7: "C",
6: "D",
# 不同的分数段,对应的评级
} if x >= 60:
return score_to_rank[x // 10] # 直接利用前面定义好的字典
return "E"
# 简单测试一下
assert calc_rank(40) == "E"
assert calc_rank(100) == "A"
map(calc_rank) # Series的map方法,既可以接受字典dict,也可以接受函数。 df.score.
0 B
1 E
2 E
3 E
4 D
5 E
Name: score, dtype: object
某些特殊的值有特殊的含义,比如被访者不在,拒绝回答,无法识别答案等等。
# 有一列数据,999和-1分别表示不同的异常情况,比如999是拒绝回答,-1是被访者不在
= pd.Series([1, 2, 999, 3, 4, -1])
df df
0 1
1 2
2 999
3 3
4 4
5 -1
dtype: int64
# replace()方法可以接受一个字典,其中定义了不同值的替代,如999 -> nan,-1 -> 0。
999: np.nan, -1: 0}) df.replace({
0 1.0
1 2.0
2 NaN
3 3.0
4 4.0
5 0.0
dtype: float64
999, -1], np.nan) # 或者多个值转为1个值: 999和-1都转为nan。 df.replace([
0 1.0
1 2.0
2 NaN
3 3.0
4 4.0
5 NaN
dtype: float64
使用以下虚拟数据,完成下列练习。全部完成后,打印最终数据。
注意:和前面的所有练习一样,这个练习也有一个小知识点需要大家自行探索, 并且题目中埋了个雷。
Age
列中的缺失值。Score
列中的缺失值。Age
列中的特殊值-1替换为平均年龄。Score
列中的特殊值9999替换为中位数。City
列中的特殊值-1替换为最常见的城市。Score
列中所有大于90分的分数都转换为”Excellent”,其他值为”Not Excellent”,并添加一列Performance
来存储这个结果。City
列中的所有城市名称转换为大写。全部完成后,打印最终数据。
import numpy as np
# 创建数据集
= {
data "ID": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
"Name": [
"Alice",
"Bob",
"Charlie",
"David",
"Eve",
"Frank",
"Grace",
"Hannah",
"Isaac",
"Jack",
"Bob",
"Grace",
],"Age": [23, -1, 22, 24, 29, np.nan, 26, 21, 24, 28, -1, 26],
"Score": [88, 92, 9999, 95, 78, 85, 90, 88, 89, 91, 92, 90],
"City": [
"New York",
"Los Angeles",
"Chicago",
-1,
"Houston",
-1,
"San Francisco",
"New York",
"Chicago",
np.nan,"Los Angeles",
"San Francisco",
],
}
= pd.DataFrame(data) df
Series是序列对象,DataFrame是二维表格对象。序列可以看成是表格的一行或者一列, 表格可以也看出由序列横向或者纵向合并而成。
Series可以由Python的列表,或者NumPy的ndarray创建。
import pandas as pd
import numpy as np
= pd.Series([5, 6, 7, 8])
a a
0 5
1 6
2 7
3 8
dtype: int64
可见,Series创建时已经自带了index。也可以在创建时指定name属性,以及自定index
= pd.Series([5, 6, 7, 8], name="A", index=list("abcd"))
a a
a 5
b 6
c 7
d 8
Name: A, dtype: int64
我们后面用默认的整数索引,因此用.reset_index()
方法重置索引。(当然,上一个cell中,不用index参数也一样)
= a.reset_index(drop=True) # drop=True会完全丢弃索引;默认为false,索引会成为index列
a a
0 5
1 6
2 7
3 8
Name: A, dtype: int64
创建另一个索引,名字叫B
= pd.Series(list("ABCD"), name="B")
b b
0 A
1 B
2 C
3 D
Name: B, dtype: object
pd.concat()
可以把Series或者DataFrame横向或者纵向地“粘贴”在一起,并且对齐行标签(横向粘贴)或者对其列标签(纵向粘贴)。
我们可以把两个Series横向合并,获得一个DataFrame。
此时,Series的name属性,会成为每一列的header。
注意,pd.concat
接收的参数是要合并的Series或者DataFrame的列表。参数axis=1
表示横向合并(列合并)。
= pd.concat([a, b], axis=1)
df df
A | B | |
---|---|---|
0 | 5 | A |
1 | 6 | B |
2 | 7 | C |
3 | 8 | D |
显然,把Series横向合并到DataFrame也是常用操作。
= pd.Series(["陈", "李", "张", "黄"], name="C")
c =1) pd.concat([df, c], axis
A | B | C | |
---|---|---|---|
0 | 5 | A | 陈 |
1 | 6 | B | 李 |
2 | 7 | C | 张 |
3 | 8 | D | 黄 |
如果index不完全相同,会发生什么?
= pd.Series(["陈", "李", "张", "黄"], name="C", index=[1, 2, 3, 4])
c =1) pd.concat([df, c], axis
A | B | C | |
---|---|---|---|
0 | 5.0 | A | NaN |
1 | 6.0 | B | 陈 |
2 | 7.0 | C | 李 |
3 | 8.0 | D | 张 |
4 | NaN | NaN | 黄 |
注意:连接操作pd.concat
会自动对齐所有要合并的对象的索引,如上一个cell。
如果你想忽略索引,只是想简单粗暴地合并,那么可以用 reset_index(drop=True)
,把2个df的索引重置成0开始的序列,即可把2个df强行粘贴到一起。当然,只要你保持2个df的索引一致,比如你把其中一个df的索引改写为另一个df的索引,也是可以的。
= pd.Series(["陈", "李", "张", "黄"], name="C", index=[1, 2, 3, 4])
c = pd.concat([df.reset_index(drop=True), c.reset_index(drop=True)], axis=1)
d d
A | B | C | |
---|---|---|---|
0 | 5 | A | 陈 |
1 | 6 | B | 李 |
2 | 7 | C | 张 |
3 | 8 | D | 黄 |
当然,纵向合并(增加行)也完全可以。以2个DataFrame为例:
先构造2个DataFrame
= d.iloc[:2]
g g
A | B | C | |
---|---|---|---|
0 | 5 | A | 陈 |
1 | 6 | B | 李 |
= d.iloc[2:]
h = h.reset_index(drop=True)
h h
A | B | C | |
---|---|---|---|
0 | 7 | C | 张 |
1 | 8 | D | 黄 |
2个DataFrame的index会原样保留,axis=0
表示纵向(上下)合并。
=0) pd.concat([g, h], axis
A | B | C | |
---|---|---|---|
0 | 5 | A | 陈 |
1 | 6 | B | 李 |
0 | 7 | C | 张 |
1 | 8 | D | 黄 |
参数ignore_index = True
则会重置index。
=0, ignore_index=True) pd.concat([g, h], axis
A | B | C | |
---|---|---|---|
0 | 5 | A | 陈 |
1 | 6 | B | 李 |
2 | 7 | C | 张 |
3 | 8 | D | 黄 |
Merge和横向的concat有点类似,但是Merge横向合并2个DataFrame的依据是某一列,或者多列。
构造2个示范数据:
# 同学信息
= pd.DataFrame(dict(id=[1, 2, 3], name=["A", "B", "C"]))
df1 df1
id | name | |
---|---|---|
0 | 1 | A |
1 | 2 | B |
2 | 3 | C |
# 数学成绩表
= pd.DataFrame(dict(id=[2, 3, 4], math=[80, 90, 95]))
df2 df2
id | math | |
---|---|---|
0 | 2 | 80 |
1 | 3 | 90 |
2 | 4 | 95 |
pd.merge(df1, df2)
接收2个DataFrame,并且会自动识别同名的列,并以同名列为标准,合并2个表。
也可以用on
参数指定。
默认情况下取2个表的同名列的交集:2个表都存在的个案,才会合并。
# 例如,把个人信息和数学成绩合并
# 同名列是id,因此merge时会自动对齐id
# 默认情况是取交集:id = 1 同学没数学成绩,因此不会被合并进来 pd.merge(df1, df2)
id | name | math | |
---|---|---|---|
0 | 2 | B | 80 |
1 | 3 | C | 90 |
pd.merge(="id"
df1, df2, on# 和上一段代码等价。id列是同名列,因此可以选择是否手动指定。 )
id | name | math | |
---|---|---|---|
0 | 2 | B | 80 |
1 | 3 | C | 90 |
参数 how = 'outer'
,可以进行外合并(取并集),缺失的部分自动填充NaN。
="outer") pd.merge(df1, df2, how
id | name | math | |
---|---|---|---|
0 | 1 | A | NaN |
1 | 2 | B | 80.0 |
2 | 3 | C | 90.0 |
3 | 4 | NaN | 95.0 |
还可以以“左表”(how ='left'
)或者“右表”(how ='right'
)为标准,只保留指定的表的个案。
="right") # 只保留df2“右表”中有的个案,左表没有的个案会填充NaN pd.merge(df1, df2, how
id | name | math | |
---|---|---|---|
0 | 2 | B | 80 |
1 | 3 | C | 90 |
2 | 4 | NaN | 95 |
merge()除了可以自动判断同名列,还可以有你指定列名。
构造一个新的df,其中学号的列名为stu_id
= pd.DataFrame(dict(stu_id=[1, 2], python=[75, 85]))
df3 df3
stu_id | python | |
---|---|---|
0 | 1 | 75 |
1 | 2 | 85 |
参数left_on
和right_on
分别指定坐标和右表的列
# 左表指定id,右表指定stu_id
="id", right_on="stu_id", how="outer") pd.merge(df1, df3, left_on
id | name | stu_id | python | |
---|---|---|---|---|
0 | 1 | A | 1.0 | 75.0 |
1 | 2 | B | 2.0 | 85.0 |
2 | 3 | C | NaN | NaN |
merge还可以串联:merge的结果是一个新的DataFrame,因此可以持续地merge下去。
= (
df4 ="outer")
df1.merge(df2, how="id", right_on="stu_id", how="outer")
.merge(df3, left_on"stu_id", axis=1)
.drop(
) df4
id | name | math | python | |
---|---|---|---|---|
0 | 1 | A | NaN | 75.0 |
1 | 2 | B | 80.0 | 85.0 |
2 | 3 | C | 90.0 | NaN |
3 | 4 | NaN | 95.0 | NaN |
见作业专用ipynb文件。
先构造一个示例数据。
# 例行公事
import pandas as pd
import numpy as np
# 创建示例代码
# 创建4个班,12位同学
= pd.DataFrame(dict(class_id=list("1234") * 3, name=list("ABCDEFGHIJKL")))
df
# 随机分配2门课的分数
"math"] = np.random.randint(50, 100, 12) # 随机整数(起点,终点,数量)
df["python"] = np.random.randint(45, 95, 12)
df["math_rank"] = df.math.map(
df[
calc_rank# 如果提示找不到calc_rank,记得把前面定义calc_rank的cell执行一下
) "python_rank"] = df.python.map(calc_rank)
df[
= df.sample(10).sort_index() # 抽取10人/随机排除2人,再按index排序
df df
class_id | name | math | python | math_rank | python_rank | |
---|---|---|---|---|---|---|
0 | 1 | A | 69 | 45 | D | E |
2 | 3 | C | 84 | 93 | B | A |
3 | 4 | D | 69 | 94 | D | A |
5 | 2 | F | 52 | 51 | E | E |
6 | 3 | G | 76 | 57 | C | E |
7 | 4 | H | 92 | 65 | A | D |
8 | 1 | I | 83 | 87 | B | B |
9 | 2 | J | 99 | 75 | A | C |
10 | 3 | K | 92 | 67 | A | D |
11 | 4 | L | 89 | 88 | B | B |
在进行分组和聚合之前,先说明一些简单统计的方法。
.describe()
:计算数值列的:个案数,均值,标准差,最小值,分位数:25%、50%(中位数)、75%,最大值.mean()
。.agg()
聚合:计算列的多种统计值。.value_counts()
:计算离散变量出现的频率。统计型描述:.describe()
:
计算数值列的:个案数,均值,标准差,最小值,分位数:25%、50%(中位数)、75%,最大值
round(2) df.describe().
math | python | |
---|---|---|
count | 10.00 | 10.00 |
mean | 80.50 | 72.20 |
std | 14.12 | 17.90 |
min | 52.00 | 45.00 |
25% | 70.75 | 59.00 |
50% | 83.50 | 71.00 |
75% | 91.25 | 87.75 |
max | 99.00 | 94.00 |
计算某个统计量,如均值:
# 支持的统计函数很多,如sum, max, std等等,不能一一列举,请用搜索引擎。
"math", "python"]].mean() df[[
math 80.5
python 72.2
dtype: float64
对多个列求多个统计量:.agg()
# 利用agg进行聚合:对于每一列,计算指定函数的结果。参数是“函数名”,是字符串,有引号。如“mean”
"math", "python"]].agg(["mean", "median"]) df[[
math | python | |
---|---|---|
mean | 80.5 | 72.2 |
median | 83.5 | 71.0 |
计算离散变量出现的频率:.value_counts()
# math_rank是一个离散型变量:ABCDE,因此可以用.value_counts()来统计变量值出现的频率。
"math_rank"]].value_counts().sort_index() df[[
math_rank
A 3
B 3
C 1
D 2
E 1
Name: count, dtype: int64
有大量操作是按分组进行的,如
这种情况一般可以采用分组和聚合的操作:
df.groupby(<分组列>).<聚合操作>
# 求班级人数: count()分组的样本数
# groupby('class_id') :按class_id分组
# count(): 对于每个组,求非NA的样本数
"class_id").count() df.groupby(
name | math | python | math_rank | python_rank | |
---|---|---|---|---|---|
class_id | |||||
1 | 2 | 2 | 2 | 2 | 2 |
2 | 2 | 2 | 2 | 2 | 2 |
3 | 3 | 3 | 3 | 3 | 3 |
4 | 3 | 3 | 3 | 3 | 3 |
# 课程平均分
# class_id 分组 -> 选择列['python','math']
# .mean() : 求分组后的均值
# .reset_index() : 把class_id从index转为一个普通列
"class_id")[["python", "math"]].mean().round().reset_index() df.groupby(
class_id | python | math | |
---|---|---|---|
0 | 1 | 66.0 | 76.0 |
1 | 2 | 63.0 | 76.0 |
2 | 3 | 72.0 | 84.0 |
3 | 4 | 82.0 | 83.0 |
# 每个班,Python课最高分的同学
# 先多重排序,看看我们要选择的人
"class_id", "python"]) df.sort_values([
class_id | name | math | python | math_rank | python_rank | |
---|---|---|---|---|---|---|
0 | 1 | A | 69 | 45 | D | E |
8 | 1 | I | 83 | 87 | B | B |
5 | 2 | F | 52 | 51 | E | E |
9 | 2 | J | 99 | 75 | A | C |
6 | 3 | G | 76 | 57 | C | E |
10 | 3 | K | 92 | 67 | A | D |
2 | 3 | C | 84 | 93 | B | A |
7 | 4 | H | 92 | 65 | A | D |
11 | 4 | L | 89 | 88 | B | B |
3 | 4 | D | 69 | 94 | D | A |
# 某个分类中,某个值最高(最低)的n个样本:先排序,再分组,再head/tail
# df.sort_index('python',ascending=False) : 整个df先按python分数逆序排序(大值在前)
# .groupby('class_id'): 按班级分组(组内已经是Python高分在前)
# .head(1): 取每组的第一个
"python", ascending=False).groupby("class_id").head(1) df.sort_values(
class_id | name | math | python | math_rank | python_rank | |
---|---|---|---|---|---|---|
3 | 4 | D | 69 | 94 | D | A |
2 | 3 | C | 84 | 93 | B | A |
8 | 1 | I | 83 | 87 | B | B |
9 | 2 | J | 99 | 75 | A | C |
如果要对每一个分组都进行多个聚合,可以用agg()
方法,传递一个list,其中包括每个函数的“函数名”
agg([ <函数名>, <函数名>,...])
# 对于python和math,求每个班的平均分,最高分
# 传递给agg()的是一个list,其中包括每个函数的“函数名”
# 分组 —> 要计算的列 -> agg(多个函数)
"class_id")[["python", "math"]].agg(["mean", "max"]).round(2) df.groupby(
python | math | |||
---|---|---|---|---|
mean | max | mean | max | |
class_id | ||||
1 | 66.00 | 87 | 76.00 | 83 |
2 | 63.00 | 75 | 75.50 | 99 |
3 | 72.33 | 93 | 84.00 | 92 |
4 | 82.33 | 94 | 83.33 | 92 |
# 也可以写自定义函数
def calc_range(x):
"""计算序列x中,最大值和最小值之差"""
return max(x) - min(x)
# 注意:转递自定义函数给agg,传递的是函数本身,而不是函数名的字符串(没有引号)
"class_id")[["python", "math"]].agg([calc_range]) df.groupby(
python | math | |
---|---|---|
calc_range | calc_range | |
class_id | ||
1 | 42 | 14 |
2 | 24 | 47 |
3 | 36 | 16 |
4 | 29 | 23 |
# 注意到,列名是“函数名”,可能不是很合理
# 可以给agg传递函数名,也可以传递一个元组 `(列名, 函数名)`
"class_id")[["python", "math"]].agg(
df.groupby("mean", "max", ("range", calc_range)]
[round() ).
python | math | |||||
---|---|---|---|---|---|---|
mean | max | range | mean | max | range | |
class_id | ||||||
1 | 66.0 | 87 | 42 | 76.0 | 83 | 14 |
2 | 63.0 | 75 | 24 | 76.0 | 99 | 47 |
3 | 72.0 | 93 | 36 | 84.0 | 92 | 16 |
4 | 82.0 | 94 | 29 | 83.0 | 92 | 23 |
如果对于每个分组,都要进行一个很复杂的操作,那么可以对分组进行循环,可以在循环体内获得该分组的数据。
对每一个组处理完毕后,append到一个空列表,然后pd.concat()即可。
# 简单循环一下,看看每个组是什么结构
for x in df.groupby("class_id"):
# x 是一个元组,第一个元素是分组的标签(序号),第二个元素是分组后的DF
print(x)
('1', class_id name math python math_rank python_rank
0 1 A 69 45 D E
8 1 I 83 87 B B)
('2', class_id name math python math_rank python_rank
5 2 F 52 51 E E
9 2 J 99 75 A C)
('3', class_id name math python math_rank python_rank
2 3 C 84 93 B A
6 3 G 76 57 C E
10 3 K 92 67 A D)
('4', class_id name math python math_rank python_rank
3 4 D 69 94 D A
7 4 H 92 65 A D
11 4 L 89 88 B B)
# 你可以进行很复杂的操作
# 但这里简单举例
= []
result
for x in df.groupby("class_id"):
# x 是一个元组,第一个元素是分组的标签(序号),第二个元素是分组后的DF
# x[1]元组的第二个元素,就是本组的数据
# 你可以每个x[1]做很复杂的操作,然后append到result后面。
# 取得math最高分的行:正序排列取最末尾行
1].sort_values("math").tail(1))
result.append(x[
pd.concat(result)
class_id | name | math | python | math_rank | python_rank | |
---|---|---|---|---|---|---|
8 | 1 | I | 83 | 87 | B | B |
9 | 2 | J | 99 | 75 | A | C |
10 | 3 | K | 92 | 67 | A | D |
7 | 4 | H | 92 | 65 | A | D |
# 列表推导也可以循环分组!
# 这里略过
print(x) for i, x in df.groupby("class_id")] [
class_id name math python math_rank python_rank
0 1 A 69 45 D E
8 1 I 83 87 B B
class_id name math python math_rank python_rank
5 2 F 52 51 E E
9 2 J 99 75 A C
class_id name math python math_rank python_rank
2 3 C 84 93 B A
6 3 G 76 57 C E
10 3 K 92 67 A D
class_id name math python math_rank python_rank
3 4 D 69 94 D A
7 4 H 92 65 A D
11 4 L 89 88 B B
[None, None, None, None]
’’’ 根据数据框df,完成以下操作: a) 计算每个班级的数学平均分 b) 找出Python成绩的最高分和最低分 c) 统计每个班级的人数 ’’’ # 答案1: # a) 按班级计算数学平均分 class_math_mean = df.groupby(‘class_id’)[‘math’].mean()
python_max = df[‘python’].max() python_min = df[‘python’].min()
class_counts = df[‘class_id’].value_counts()
’’’ 请完成以下筛选操作: a) 找出数学成绩大于80分的学生 b) 找出数学和Python都及格(>=60)的学生 c) 统计每个班级有多少人数学成绩大于班级平均分 ’’’ # 答案2: # a) 数学成绩大于80分的学生 good_math = df[df[‘math’] > 80]
pass_both = df[(df[‘math’] >= 60) & (df[‘python’] >= 60)]
class_means = df.groupby(‘class_id’)[‘math’].transform(‘mean’) above_avg = df[df[‘math’] > class_means].groupby(‘class_id’).size()
’’’ 请完成以下操作: a) 计算每个班级中数学成绩的最好名次 b) 找出每个班级Python成绩的前2名 c) 计算每个班级的数学和Python平均分,并按总平均分从高到低排序 ’’’ # 答案3: # a) 每个班级数学最好名次 best_math_rank = df.groupby(‘class_id’)[‘math_rank’].min()
top2_python = df.sort_values(‘python’, ascending=False).groupby(‘class_id’).head(2)
class_avg = df.groupby(‘class_id’)[[‘math’, ‘python’]].mean() class_avg[‘total_avg’] = class_avg.mean(axis=1) class_avg_sorted = class_avg.sort_values(‘total_avg’, ascending=False)
’’’ 请使用数据透视表完成以下操作: a) 创建一个数据透视表,显示每个班级的数学和Python平均分 b) 计算每个班级的及格率(分别计算数学和Python) c) 找出数学和Python成绩差距最大的班级 ’’’ # 答案4: # a) 班级平均分透视表 class_scores = pd.pivot_table(df, values=[‘math’, ‘python’], index=‘class_id’, aggfunc=‘mean’)
def pass_rate(x): return (x >= 60).mean() * 100
class_pass_rates = pd.pivot_table(df, values=[‘math’, ‘python’], index=‘class_id’, aggfunc=pass_rate)
class_scores[‘score_diff’] = abs(class_scores[‘math’] - class_scores[‘python’]) max_diff_class = class_scores[‘score_diff’].idxmax()
’’’ 请完成以下高级分组运算: a) 为每个学生计算其数学和Python成绩与班级平均分的差值 b) 计算每个班级的数学和Python成绩的中位数、标准差 c) 找出每个班级中数学比Python成绩好(分数高)的学生人数 ’’’ # 答案5: # a) 计算与班级平均分的差值 class_math_mean = df.groupby(‘class_id’)[‘math’].transform(‘mean’) class_python_mean = df.groupby(‘class_id’)[‘python’].transform(‘mean’) df[‘math_diff_from_mean’] = df[‘math’] - class_math_mean df[‘python_diff_from_mean’] = df[‘python’] - class_python_mean
class_stats = df.groupby(‘class_id’).agg({ ‘math’: [‘median’, ‘std’], ‘python’: [‘median’, ‘std’] })
better_at_math = df[df[‘math’] > df[‘python’]].groupby(‘class_id’).size()
典型的时间序列数据就是股价。在Pandas中,一般把时间信息(表示时间的列),转为索引index,便于我们操作。
# 例行公事的导入
import pandas as pd
import numpy as np
# 构造一个普通的DF,但其中一列是字符串形式的时间
# 我们平时读取的数据,往往也是这种形式
= [
date "2021-01-01",
"2021-03-01",
"2021-03-05",
"2022-01-01",
"2022-03-01",
"2022-03-05",
]
= pd.DataFrame(dict(date=date, x=np.arange(len(date)) + 8))
df df
date | x | |
---|---|---|
0 | 2021-01-01 | 8 |
1 | 2021-03-01 | 9 |
2 | 2021-03-05 | 10 |
3 | 2022-01-01 | 11 |
4 | 2022-03-01 | 12 |
5 | 2022-03-05 | 13 |
# date 的类型是object(即字符串str) df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 2 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 date 6 non-null object
1 x 6 non-null int64
dtypes: int64(1), object(1)
memory usage: 228.0+ bytes
一般的预处理:
= pd.to_datetime(df.date)
df.date "date", inplace=True)
df.set_index( df
x | |
---|---|
date | |
2021-01-01 | 8 |
2021-03-01 | 9 |
2021-03-05 | 10 |
2022-01-01 | 11 |
2022-03-01 | 12 |
2022-03-05 | 13 |
按整数序号索引,切片等,和一般的DataFrame类似。
# 索引
1] df.iloc[
x 9
Name: 2021-03-01 00:00:00, dtype: int64
# 切片
2:4] df.iloc[
x | |
---|---|
date | |
2021-03-05 | 10 |
2022-01-01 | 11 |
对于时间序列数据(index是datetime的DataFrame),按索引取值(.loc()
)有一些特殊的便利:可以接受任何“可以解释为时间”的字符串:
"2022-01-01"] # 按行标签取值,标准做法,和一般的DataFrame一样 df.loc[
x 11
Name: 2022-01-01 00:00:00, dtype: int64
# 写成其他格式(或者其他地区的标准)也可以被loc[]理解
"20220101"]
df.loc["01/01/2022"] df.loc[
x 11
Name: 2022-01-01 00:00:00, dtype: int64
只使用月份或者年份也可以
"2021"] # 提取2021年所有数据 df.loc[
x | |
---|---|
date | |
2021-01-01 | 8 |
2021-03-01 | 9 |
2021-03-05 | 10 |
"2021-03"] # 提取2021年3月所有数据 df.loc[
x | |
---|---|
date | |
2021-03-01 | 9 |
2021-03-05 | 10 |
# 切片也可以接受这类“看起来像日期”的字符串
# 其他的用法和一般的DataFrame类似。
"2021":"2022-1"] # 2021年(全年)到2022年1月的数据 df.loc[
x | |
---|---|
date | |
2021-01-01 | 8 |
2021-03-01 | 9 |
2021-03-05 | 10 |
2022-01-01 | 11 |
时间序列的运算,自然也会以时间为基准。
# 构造另一个DataFrame,和df有所不同
= df.copy()
df2 "2021-03-05", "2022-03-01"], axis=0, inplace=True)
df2.drop(["2022-09-01")] = 14
df2.loc[pd.to_datetime(= ["y"]
df2.columns = df2.y / 2
df2.y df2
y | |
---|---|
date | |
2021-01-01 | 4.0 |
2021-03-01 | 4.5 |
2022-01-01 | 5.5 |
2022-03-05 | 6.5 |
2022-09-01 | 7.0 |
横向concat会自动对齐时间
= pd.concat([df, df2], axis=1) # 横向合并(concat),会自动对齐时间
df3 df3
x | y | |
---|---|---|
date | ||
2021-01-01 | 8.0 | 4.0 |
2021-03-01 | 9.0 | 4.5 |
2021-03-05 | 10.0 | NaN |
2022-01-01 | 11.0 | 5.5 |
2022-03-01 | 12.0 | NaN |
2022-03-05 | 13.0 | 6.5 |
2022-09-01 | NaN | 7.0 |
# 同样,一般的列运算也会对齐时间
"result"] = (df3.x / 3 - np.sqrt(df3.y)).round(2)
df3[ df3
x | y | result | |
---|---|---|---|
date | |||
2021-01-01 | 8.0 | 4.0 | 0.67 |
2021-03-01 | 9.0 | 4.5 | 0.88 |
2021-03-05 | 10.0 | NaN | NaN |
2022-01-01 | 11.0 | 5.5 | 1.32 |
2022-03-01 | 12.0 | NaN | NaN |
2022-03-05 | 13.0 | 6.5 | 1.78 |
2022-09-01 | NaN | 7.0 | NaN |
用 pd.date_range()
= pd.date_range("2022-01-15", "2022-02-15") # 定义起点和终点,默认的频率是“D”(日)
x x
DatetimeIndex(['2022-01-15', '2022-01-16', '2022-01-17', '2022-01-18',
'2022-01-19', '2022-01-20', '2022-01-21', '2022-01-22',
'2022-01-23', '2022-01-24', '2022-01-25', '2022-01-26',
'2022-01-27', '2022-01-28', '2022-01-29', '2022-01-30',
'2022-01-31', '2022-02-01', '2022-02-02', '2022-02-03',
'2022-02-04', '2022-02-05', '2022-02-06', '2022-02-07',
'2022-02-08', '2022-02-09', '2022-02-10', '2022-02-11',
'2022-02-12', '2022-02-13', '2022-02-14', '2022-02-15'],
dtype='datetime64[ns]', freq='D')
= pd.date_range("2022-01-15", "2022-06-15", freq="M") # 频率M:月
x x
DatetimeIndex(['2022-01-31', '2022-02-28', '2022-03-31', '2022-04-30',
'2022-05-31'],
dtype='datetime64[ns]', freq='M')
# periods:指定日期的数量
# 频率B:工作日
= pd.date_range("2022-01-15", periods=5, freq="B") # 2022-01-15起的5个工作日
x x
DatetimeIndex(['2022-01-17', '2022-01-18', '2022-01-19', '2022-01-20',
'2022-01-21'],
dtype='datetime64[ns]', freq='B')
# 获得年、月、日的序列
print(x.year)
print(x.month)
print(x.day)
Index([2022, 2022, 2022, 2022, 2022], dtype='int32')
Index([1, 1, 1, 1, 1], dtype='int32')
Index([17, 18, 19, 20, 21], dtype='int32')
构造一个虚拟股价序列,计算(一阶)滞后、(一阶)差分和(日)回报率。
# 构造一个虚拟的价格序列
= pd.DataFrame(dict(price=[10.9, 12.1, 11.3, 11.6, 12.7]), index=x) # x见上一节。
df df
price | |
---|---|
2022-01-17 | 10.9 |
2022-01-18 | 12.1 |
2022-01-19 | 11.3 |
2022-01-20 | 11.6 |
2022-01-21 | 12.7 |
# n阶滞后:默认是1阶
"price_1"] = df.price.shift()
df[ df
price | price_1 | |
---|---|---|
2022-01-17 | 10.9 | NaN |
2022-01-18 | 12.1 | 10.9 |
2022-01-19 | 11.3 | 12.1 |
2022-01-20 | 11.6 | 11.3 |
2022-01-21 | 12.7 | 11.6 |
# 差分:默认是1阶
# 可以:1. 用price - price_1,2. 用price.diff()方法。
"diff"] = df.price.diff()
df[ df
price | price_1 | diff | |
---|---|---|---|
2022-01-17 | 10.9 | NaN | NaN |
2022-01-18 | 12.1 | 10.9 | 1.2 |
2022-01-19 | 11.3 | 12.1 | -0.8 |
2022-01-20 | 11.6 | 11.3 | 0.3 |
2022-01-21 | 12.7 | 11.6 | 1.1 |
# 百分比变化(日回报率)
# 可以 1. 用一阶差分除以一阶滞后。 或者2. 直接用price.pct_change()方法。
"ret"] = df["diff"] / df["price_1"]
df[
"ret_2"] = df["price"].pct_change()
df[ df
price | price_1 | diff | ret | ret_2 | |
---|---|---|---|---|---|
2022-01-17 | 10.9 | NaN | NaN | NaN | NaN |
2022-01-18 | 12.1 | 10.9 | 1.2 | 0.110092 | 0.110092 |
2022-01-19 | 11.3 | 12.1 | -0.8 | -0.066116 | -0.066116 |
2022-01-20 | 11.6 | 11.3 | 0.3 | 0.026549 | 0.026549 |
2022-01-21 | 12.7 | 11.6 | 1.1 | 0.094828 | 0.094828 |
# 从回报率计算价格
# 利用comprod():序列的累积连乘
# 见:https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.cumprod.html
# 从1开始的价格:
# 价格1 = (1 + 回报率).cumprod()
"price_2"] = (1 + df["ret"].fillna(0)).cumprod()
df[ df
price | price_1 | diff | ret | ret_2 | price_2 | |
---|---|---|---|---|---|---|
2022-01-17 | 10.9 | NaN | NaN | NaN | NaN | 1.000000 |
2022-01-18 | 12.1 | 10.9 | 1.2 | 0.110092 | 0.110092 | 1.110092 |
2022-01-19 | 11.3 | 12.1 | -0.8 | -0.066116 | -0.066116 | 1.036697 |
2022-01-20 | 11.6 | 11.3 | 0.3 | 0.026549 | 0.026549 | 1.064220 |
2022-01-21 | 12.7 | 11.6 | 1.1 | 0.094828 | 0.094828 | 1.165138 |
# 简单验算: 除了第一天,序列price和price_2的日回报应该相同。
round(3) == df.ret.round(3) df.price_2.pct_change().
2022-01-17 False
2022-01-18 True
2022-01-19 True
2022-01-20 True
2022-01-21 True
Freq: B, dtype: bool
不同频率周期的数据相互转换:如日频数据到周频或者月频(日线到周线、月线等),或者反过来。
使用.resample()
。
df
# 构造一个3个月的日线序列
= pd.date_range("2022-01-01", "2022-03-31")
ts = pd.DataFrame(dict(value=np.arange(len(ts))), index=ts)
df df
value | |
---|---|
2022-01-01 | 0 |
2022-01-02 | 1 |
2022-01-03 | 2 |
2022-01-04 | 3 |
2022-01-05 | 4 |
... | ... |
2022-03-27 | 85 |
2022-03-28 | 86 |
2022-03-29 | 87 |
2022-03-30 | 88 |
2022-03-31 | 89 |
90 rows × 1 columns
需要指定周期和归纳的方法,如
# 重采样为周"W",取每周的最后一个值
"W").last().head() # 周数据的最后一个值,默认是取"周日"为最后一天 df.resample(
value | |
---|---|
2022-01-02 | 1 |
2022-01-09 | 8 |
2022-01-16 | 15 |
2022-01-23 | 22 |
2022-01-30 | 29 |
# 可以设置以周五为最后一天,这样就可以取得全部的周五
# 符合世界上主要国家每周的最后一个工作日,比如股票的周线收盘价
"W-FRI").last().head() df.resample(
value | |
---|---|
2022-01-07 | 6 |
2022-01-14 | 13 |
2022-01-21 | 20 |
2022-01-28 | 27 |
2022-02-04 | 34 |
可用的每周结束日期包括
‘W-SUN’ 或 ‘W’: 周日
‘W-MON’: 周一
‘W-TUE’: 周二
‘W-WED’: 周三
‘W-THU’: 周四
‘W-FRI’: 周五
‘W-SAT’: 周六
# 重采样为月线M,取区间内所有值的和
"M").sum() # 月数据的求和 df.resample(
value | |
---|---|
2022-01-31 | 465 |
2022-02-28 | 1246 |
2022-03-31 | 2294 |
可以用的函数包括:
mean()
: 计算时间段内的平均值。
sum()
: 计算时间段内的总和。
max()
: 找到时间段内的最大值。
min()
: 找到时间段内的最小值。
count()
: 计算时间段内的观测数。
first()
: 获取时间段内的第一个值。
last()
: 获取时间段内的最后一个值。
median()
: 计算时间段内的中位数。
std()
: 计算时间段内的标准差。
var()
: 计算时间段内的方差。
ohlc()
: 为金融数据提供开盘价(open)、最高价(high)、最低价(low)、收盘价(close)的聚合。
import pandas as pd
import numpy as np
构造示例数据
注意:
df['datetime']
列是“日期和时间”格式(Pandas默认),有日期和时间的信息,这会反映在保存后的数据中。df['datetime'].dt.date
可以从中获得仅有“日期”信息(见前面的“时间方法”)。= pd.DataFrame(
df dict(
=pd.date_range("2022-01-15", "2022-01-19"),
datetime=range(1, 6),
number=list("ABCDE"),
name=np.random.randn(5),
value
)
)"date"] = df["datetime"].dt.date
df[ df
datetime | number | name | value | date | |
---|---|---|---|---|---|
0 | 2022-01-15 | 1 | A | -0.759154 | 2022-01-15 |
1 | 2022-01-16 | 2 | B | -1.685706 | 2022-01-16 |
2 | 2022-01-17 | 3 | C | 0.266500 | 2022-01-17 |
3 | 2022-01-18 | 4 | D | -1.480555 | 2022-01-18 |
4 | 2022-01-19 | 5 | E | 1.214168 | 2022-01-19 |
使用df.to_excel()
和df.to_csv()
等方法,可以把df
保存为文件。
index
也保存为列,并且可能没有列名(注意看index有没有名字)# 把df保存到工作目录(本ipynb文件所在目录)下的data文件夹下的output.xlsx文件中
# 默认参数,会连带保存index一起保存。如果index无名称,则就会产生一系列无名列。
"data/output.xlsx") df.to_excel(
# 丢弃index
# 如果index没有价值的信息,或者有价值的信息已经通过reset_index()转为普通列,则可以放心丢弃
"data/output_noindex.xlsx", index=False) df.to_excel(
你可以打开这output.xlsx
和output_noindex
进行对比。