前言
本章的内容为Pandas基础
注,本次打卡所以用到的数据都放在了同级目录下的data文件夹中:
一、文件的读取和写入
1.文件读取
#读csv 逗号分隔值
df_csv = pd.read_csv('data/my_csv.csv')
print(df_csv)col1 col2 col3 col4 col5
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
#读txt
df_txt= pd.read_table('data/my_table.txt')
print(df_txt)col1 col2 col3 col4
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
#读excel
df_excel= pd.read_excel('data/my_excel.xlsx')
print(df_excel)col1 col2 col3 col4 col5
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
安装2.0.1版本的xlrd会报错:
原因是最近xlrd更新到了2.0.1版本,只支持.xls文件。所以pandas.read_excel(‘xxx.xlsx’)会报错。
解决办法:
pip install xlrd==1.2.0
1)header参数的用法:
#读txt
df_txt= pd.read_table('data/my_table.txt')
print(df_txt)col1 col2 col3 col4
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
# head参数使用 效果是在第一行填入新的索引
df_txt= pd.read_table('data/my_table.txt',header=None)
print(df_txt)0 1 2 3
0 col1 col2 col3 col4
1 2 a 1.4 apple 2020/1/1
2 3 b 3.4 banana 2020/1/2
3 6 c 2.5 orange 2020/1/5
4 5 d 3.2 lemon 2020/1/7
2)index_col参数用法
#读csv 逗号分隔值
df_csv = pd.read_csv('data/my_csv.csv')
print(df_csv)col1 col2 col3 col4 col5
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
#读csv 逗号分隔值 使用index_col col3-5自动作为列索引?
df_csv = pd.read_csv('data/my_csv.csv',index_col=['col1','col2'])
print(df_csv)col3 col4 col5
col1 col2
2 a 1.4 apple 2020/1/1
3 b 3.4 banana 2020/1/2
6 c 2.5 orange 2020/1/5
5 d 3.2 lemon 2020/1/7
3)index_col参数用法
#读txt
df_txt= pd.read_table('data/my_table.txt')
print(df_txt)col1 col2 col3 col4
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
df_txt= pd.read_table('data/my_table.txt',usecols=['col1','col2'])
print(df_txt)col1 col2
0 2 a
1 3 b
2 6 c
3 5 d
4)parse_dates参数用法
#读csv 逗号分隔值
df_csv = pd.read_csv('data/my_csv.csv')
print(df_csv)col1 col2 col3 col4 col5
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
#读csv 逗号分隔值
df_csv = pd.read_csv('data/my_csv.csv',parse_dates=['col5'])
print(df_csv)col1 col2 col3 col4 col5
0 2 a 1.4 apple 2020-01-01
1 3 b 3.4 banana 2020-01-02
2 6 c 2.5 orange 2020-01-05
3 5 d 3.2 lemon 2020-01-07
5)nrows参数用法
#读excel
df_excel= pd.read_excel('data/my_excel.xlsx')
print(df_excel)col1 col2 col3 col4 col5
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
#读excel 设定行数
df_excel= pd.read_excel('data/my_excel.xlsx',nrows=3)
print(df_excel)col1 col2 col3 col4 col5
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
6)sep参数用法
#直接读
df_txt = pd.read_table('data/my_table_special_sep.txt')
print(df_txt)col1 |||| col2
0 TS |||| This is an apple.
1 GQ |||| My name is Bob.
2 WT |||| Well done!
3 PT |||| May I help you?
#使用sep
df_txt = pd.read_table('data/my_table_special_sep.txt',sep='\|\|\|\|',engine='python')
print(df_txt)col1 col2
0 TS This is an apple.
1 GQ My name is Bob.
2 WT Well done!
3 PT May I help you?
2.数据写入
刚才我们读出来的DataFrame格式的数据相当于一份原文件的copy,当我们修改后,需要将它与原文件进行同步,我们以.xlsx文件为例,了解一下数据写入过程:
例一、2.1 修改excel中的列名
先记录一下原始文档的数据:
使用pip install openpyxl
解决,注意这里不需要import新的包:
原因出在文件打开被占用
我们关掉刚才打开的excel文件后继续:
#读excel函数 打印并返回excel中的df数据
>>> def getExcel(excel_path):
>>> df_excel= pd.read_excel(excel_path)
>>> return df_excel
>>> excel_path = 'data/my_excel.xlsx'
>>> df_excel = getExcel(excel_path)
>>> print(df_excel)col1 col2 col3 col4 col5
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
#修改第一列列名
>>> df_excel.rename(columns={'col1':'hys'},inplace=True)
#df中数据
>>> print(df_excel)hys col2 col3 col4 col5
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
#数据写入
>>> df_excel.to_excel(excel_path,index=False)
#查看原文件
>>> print(getExcel(excel_path))hys col2 col3 col4 col5
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
再查看excel的数据:
另说明,to_excel()方法
中index的作用,如果设置成True(默认为True),会在df的基础上在最左侧加一列,比如:
显然这不是我们想要的,所以需要在无实际需要的时候,最好把index设置成False。
例一、2.2 将txt文件保存为csv文件并指定分隔符
#读txt
>>> df_txt= pd.read_table('data/my_table.txt')
>>> print(df_txt)col1 col2 col3 col4
0 2 a 1.4 apple 2020/1/1
1 3 b 3.4 banana 2020/1/2
2 6 c 2.5 orange 2020/1/5
3 5 d 3.2 lemon 2020/1/7
>>> df_txt.to_csv('data/my_txt_20201219.txt',sep=',',index=False)
我们采用的分隔符是逗号,注意这里sep只能设置成一个字符,否则会报错:
查看文件:
例一、2.3 表格数据转成markdown格式
#读csv
>>> df_csv = pd.read_csv('data/my_csv.csv')
>>> print(df_csv.to_markdown())
产生报错:
通过pip install tabulate
安装:
再次运行:
#读csv
>>> df_csv = pd.read_csv('data/my_csv.csv')
>>> res = df_csv.to_markdown()
>>> print(type(df_csv))
<class 'pandas.core.frame.DataFrame'>
>>> print(type(res))
<class 'str'>
>>> print(res)
| | col1 | col2 | col3 | col4 | col5 |
|---:|-------:|:-------|-------:|:-------|:---------|
| 0 | 2 | a | 1.4 | apple | 2020/1/1 |
| 1 | 3 | b | 3.4 | banana | 2020/1/2 |
| 2 | 6 | c | 2.5 | orange | 2020/1/5 |
| 3 | 5 | d | 3.2 | lemon | 2020/1/7 |
可以看到,生成的markdown格式数据类型是 str 类型
我们来一个套娃测试,因为我现在编辑博客的格式就是markdown,把上面的输出放在博客里试一下:
col1 | col2 | col3 | col4 | col5 | |
---|---|---|---|---|---|
0 | 2 | a | 1.4 | apple | 2020/1/1 |
1 | 3 | b | 3.4 | banana | 2020/1/2 |
2 | 6 | c | 2.5 | orange | 2020/1/5 |
3 | 5 | d | 3.2 | lemon | 2020/1/7 |
验证成功。
例一、2.4 表格数据转成latex格式
在data文件夹下新建一个包含‘a+b=c’的csv文件:
#读csv
>>> df_csv = pd.read_csv('data/my_latex.csv')
>>> res = df_csv.to_latex()
>>> print(type(df_csv))
<class 'pandas.core.frame.DataFrame'>
>>> print(type(res))
<class 'str'>
>>> print(res)
\begin{tabular}{ll}
\toprule
Empty DataFrame
Columns: Index(['a+b = c'], dtype='object')
Index: Index([], dtype='object') \\
\bottomrule
\end{tabular}
TODO1:在Latex编辑器中验证。
二、基本数据结构
在上一章我们提到了,Pandas是基于Numpy构建的。Numpy有自己独特的ndarray数据类型,同样的,Pandas也有属于自己的两种基本的数据类型——Series 和 DataFrame
下面让我们分别来介绍:
1.Series
class pandas.Series(data=None, index=None, dtype=None, name=None, copy=False, fastpath=False)
One-dimensional ndarray with axis labels
上面是官方文档对Series的介绍,翻译过来就是带有轴标签的一维ndarray。
参数解释:
- data:传入的数据
- index:数据对应的标签
- dtype:用作指定Series的输出数据类型,如果不指定会根据data去推断
- name:Series的名字
- copy:是否复制data
- fastpath:是否快速精简模式
我们来举几个例子:
例二、1.1 创建Series数据
#定义data和index
>>> data = ['hys',25,'Beijing']
>>> index = ['name','age','loc']
#创建Series 方法一
>>> s = pd.Series(data,index,name='info')
#打印
print(s)
name hys
age 25
loc Beijing
Name: info, dtype: object
#创建Series 方法二
>>> s2 = pd.Series({'name':'hys','age': 25,'loc':'Beijing'},name='info')
>>> print(s2)
name hys
age 25
loc Beijing
Name: info, dtype: object
定义的s代表一个人的信息,其中包含3个标签(属性)。方法一是分别指定data和index来创建Series类型数据,而方法二是直接传入一个字典一并传入name和index。
Series常用属性:
属性 | 说明 |
---|---|
values | 获取数组 |
index | 获取索引 |
name | values的name |
index.name | 索引的name |
dtype | 数据类型 |
shape | 数据的shape |
Series类型数据的属性绝大部分都可以通过.属性名的方式来访问,下面举几个常用的属性:
例二、1.2 查看Series数据的属性
>>> s2 = pd.Series({'name':'hys','age': 25,'loc':'Beijing'},name='info')
>>> print(s2)
name hys
age 25
loc Beijing
Name: info, dtype: object#访问values属性
>>> print(s2.values)
['hys' 25 'Beijing']#访问index属性
>>> print(s2.index)
Index(['name', 'age', 'loc'], dtype='object')#访问dtype属性
>>> print(s2.dtype)
object#访问name属性
>>> print(s2.name)
info#访问shape属性
>>> print(s2.shape)
(3,)#通过index名来访问对应数据
>>> print(s2['name'])
hys
注意,在上面最后一个例子中我们使用了[]
方式访问了Series数据指定index下的数据,它的调用方式类似于Python中的字典:
>>> infoDict = {'name':'hys','age': 25,'loc':'Beijing'}
>>> print(infoDict['loc'])
Beijing
与ndarray相似,Series数据类型中也有提前定义好的方法供我们使用,我们在这不进行单独讲解,会在本文第三节中一并讲解Series和DataFrame中常用的方法。
2.DataFrame
class pandas.DataFrame(data=None, index=None, columns=None, dtype=None, copy=False)
Two-dimensional, size-mutable, potentially heterogeneous tabular data.
官方对DataFrame的解释是,二维大小可变的潜在异构表格数据。
相比于Series,DataFrame中的参数少了一个name和fastpath,多了一个columns,columns是列标签的意思。
举例说明:
例二、2.1 创建DataFrame数据
>>> data = [['hys',25,'Beijing'],['xmy',25,'Shanghai']]
>>> index = ['01','02']
>>> columns = ['name','age','loc']
#创建DataFrame 方法一
>>> df = pd.DataFrame(data,index,columns)
>>> print(df)name age loc
01 hys 25 Beijing
02 xmy 25 Shanghai
#创建DataFrame 方法二
>>> dataDict = {'name':['hys','xmy'],'age':[25,25],'loc':['Beijing','Shanghai']}
>>> df2 = pd.DataFrame(dataDict)
>>> print(df2)name age loc
0 hys 25 Beijing
1 xmy 25 Shanghai
同样地,我们也可以利用两种方式创建DataFrame型数据,上面的方法一仍然是常规方法,index和columns分别代表视觉上的行索引和列索引。方法二依然采用字典方式构建,其中字典的键名代表columns,index默认为0,1,…直到任意键值列表的长度-1。
同样地,我们也可以利用.属性名、[行索引名]、[列索引名]去访问指定的属性:
例二、2.2 查看DataFrame数据的属性
>>> dataDict = {'name':['hys','xmy'],'age':[25,25],'loc':['Beijing','Shanghai']}
>>> df2 = pd.DataFrame(dataDict)
>>> print(df2)name age loc
0 hys 25 Beijing
1 xmy 25 Shanghai#访问values属性
>>> print(df2.values)
[['hys' 25 'Beijing']['xmy' 25 'Shanghai']]#访问index属性
>>> print(df2.index)
RangeIndex(start=0, stop=2, step=1)#访问culumns属性
>>> print(df2.columns)
Index(['name', 'age', 'loc'], dtype='object')#访问dtypes属性
>>> print(df2.dtypes)
name object
age int64
loc object
dtype: object#访问shape属性
>>> print(df2.shape)
(2, 3)#通过columns名来访问对应数据
>>> print(df2['name'],type(df2['name']))
0 hys
1 xmy
Name: name, dtype: object <class 'pandas.core.series.Series'>
这里要注意DataFrame数据是没有dtype属性的,取而代之的是dtypes属性,它返回的是每一行单独的dtype和整个DataFrame整体的dtype。
另外值得注意的一点是,型如df2['name']
的列访问得到的数据类型是Series.
三、常用基本函数
由于从某种程度上可以把Series数据当做DataFrame数据的子集,所以在本节我们以DataFrame为主介绍pandas数据类型的常用基本函数。
采用的数据集来自data文件夹下的learn_pandas.csv:
利用上一节的知识简单查看一下数据集的重要属性:
#读入数据
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
#查看数据的shape
>>> print(df_data.shape)
(200, 10)#查看列标签
>>> print(df_data.columns)
Index(['School', 'Grade', 'Name', 'Gender', 'Height', 'Weight', 'Transfer','Test_Number', 'Test_Date', 'Time_Record'],dtype='object')
可以看到,所用数据集的shape为(200,10),共10个列标签,且没有设置行标签。
1.汇总函数
DataFrame.head(n=5)
DataFrame.tail(n=5)
head()方法
和tail()方法
分别返回前n行和后n行的数据,默认n=5:
例三、1.1 按行返回DataFrame数据
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[df_data.columns[:7]]
>>> print(df_data.head())
>>> print(df_data.tail())
例三、1.2 返回DataFrame数据信息概括和列统计量
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[df_data.columns[:7]]
>>> print(df_data.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 200 entries, 0 to 199
Data columns (total 7 columns):# Column Non-Null Count Dtype
--- ------ -------------- ----- 0 School 200 non-null object 1 Grade 200 non-null object 2 Name 200 non-null object 3 Gender 200 non-null object 4 Height 183 non-null float645 Weight 189 non-null float646 Transfer 188 non-null object
dtypes: float64(2), object(5)
memory usage: 11.1+ KB
None
>>> print(df_data.describe())Height Weight
count 183.000000 189.000000
mean 163.218033 55.015873
std 8.608879 12.824294
min 145.400000 34.000000
25% 157.150000 46.000000
50% 161.900000 51.000000
75% 167.500000 65.000000
max 193.900000 89.000000
2.特征统计函数
如果不想返回describe()方法
返回的全部统计量,我们可以使用单独的统计方法进行计算,默认为按列统计(axis=0),也可以指定axis的值:
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
#选取身高和体重列
>>> df_data = df_data[['Height','Weight']]
#统计非缺失值个数
>>> print(df_data.count())
Height 183
Weight 189
dtype: int64#计算均值
>>> print(df_data.mean())
Height 163.218033
Weight 55.015873
dtype: float64#计算标准差
>>> print(df_data.std())
Height 8.608879
Weight 12.824294
dtype: float64#最小值
>>> print(df_data.min())
Height 145.4
Weight 34.0
dtype: float64#最大值
>>> print(df_data.max())
Height 193.9
Weight 89.0
dtype: float64#四分位数 25%和75%百分位
>>> print(df_data.quantile(0.25))
Height 157.15
Weight 46.00
Name: 0.25, dtype: float64
>>> print(df_data.quantile(0.75))
Height 167.5
Weight 65.0
Name: 0.75, dtype: float64#返回最大最小值的行索引
>>> print(df_data.idxmax())
Height 193
Weight 2
dtype: int64
>>> print(df_data.idxmin())
Height 143
Weight 49
dtype: int64
3.唯一值函数
unique()方法
可以获得由唯一值组成的数组,nunique()方法
返回的是唯一值的个数:
>>> df_data_school = pd.read_csv('./data/learn_pandas.csv')['School']
>>> print(df_data_school.unique())
['Shanghai Jiao Tong University' 'Peking University' 'Fudan University''Tsinghua University']
>>> print(df_data_school.nunique())
4
>>> print(len(df_data_school.unique()))
4
不难看出,x.nunique()
和len(x.unique())
是等价的。
value_counts()方法
可以查看每个唯一值对应出现的次数:
>>> print(df_data_school.value_counts())
Tsinghua University 69
Shanghai Jiao Tong University 57
Fudan University 40
Peking University 34
Name: School, dtype: int64
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[['Gender','Transfer','Name']]
>>> print(df_data.drop_duplicates(['Gender', 'Transfer']))Gender Transfer Name
0 Female N Gaopeng Yang
1 Male N Changqiang You
12 Female NaN Peng You
21 Male NaN Xiaopeng Shen
36 Male Y Xiaojuan Qin
43 Female Y Gaoli Feng
默认drop_duplicates()方法
参数keep为first,代表展示第一次出现的行,值为last表示展示最后一次出现的行。
易知这两种情况返回的行数为每列属性种类的累积。
duplicated()方法
返回的是对应列的值是否重复,类型为布尔类型:
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[['Gender','Transfer','Name']]
>>> print(df_data.duplicated(['Gender', 'Transfer']).head(6))
0 False
1 False
2 True
3 True
4 True
5 True
dtype: bool
返回结果代表的是当前行的’Gender’和’Transfer’的组合是否从未出现过。
4.替换函数
1)映射替换
replace()方法
可以用于映射替换:
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> replace_dict = {'Male':'男','Female':'女'}
>>> print(df_data['Gender'].replace(replace_dict).head(6))
0 女
1 男
2 男
3 女
4 男
5 女
Name: Gender, dtype: object
我们还可以通过锁定替换的值后设定replace的method参数指定替换对齐方式,‘ffill’代表‘forward’向前,‘bfill’代表backward向后:
>>> data = pd.Series(np.random.randint(0,3,5))
>>> print(data)
0 0
1 2
2 0
3 2
4 1
dtype: int32
>>> print(data.replace([1],method='ffill'))
0 0
1 2
2 0
3 2
4 2
dtype: int32
>>> print(data.replace([1],method='bfill'))
0 0
1 2
2 0
3 2
4 1
dtype: int32
我们可以看到,当被替换值处于边界状态下,后面无值可替,它会保持不变。
2)逻辑替换
where()方法和mask()方法的作用正好是相反的,前者替换不满足条件的值,后者替换满足条件的值,默认将被替换值变为NaN类型:
>>> data = pd.Series(np.random.randint(0,3,5))
>>> print(data)
0 0
1 1
2 0
3 0
4 0
dtype: int32
>>> print(data .where(data<1))
0 0.0
1 NaN
2 0.0
3 0.0
4 0.0
dtype: float64
>>> print(data .mask(data<1))
0 NaN
1 1.0
2 NaN
3 NaN
4 NaN
dtype: float64
观察到一个小小的细节,就是这两种方法都使原始数据类型从int32变为了float64。
3)数值替换
这里介绍3个方法:round()
、abs()
、clip()
,分别代表取整、求绝对值和截断:
>>> data = pd.Series([1.5,-1.6,1.4,-1.3])
>>> print(data)
0 1.5
1 -1.6
2 1.4
3 -1.3
dtype: float64
>>> print(data.round(0))
0 2.0
1 -2.0
2 1.0
3 -1.0
dtype: float64
>>> print(data.abs())
0 1.5
1 1.6
2 1.4
3 1.3
dtype: float64
>>> print(data.clip(-1,1))
0 1.0
1 -1.0
2 1.0
3 -1.0
dtype: float64
我们可以注意到,round()方法
采用的是四舍五入原则。
练一练
题目:在 clip 中,超过边界的只能截断为边界值,如果要把超出边界的替换为自定义的值,应当如何做?
超出边界的值替换成指定的值:
思路:利用where
进行多条件判断,并指定替换值
>>> data = pd.Series([1.5,-1.6,1.4,-1.3])
>>> print(data)
>>> print(data.where((data>-1) & (data <2),100))
0 1.5
1 -1.6
2 1.4
3 -1.3
dtype: float64
0 1.5
1 100.0
2 1.4
3 100.0
dtype: float64
5.排序函数
1)值排序
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[['Grade', 'Name', 'Height', 'Weight']].set_index(['Grade','Name'])
#默认为True 代表升序排列
>>> print(df_data.sort_values('Height').head())Height Weight
Grade Name
Junior Xiaoli Chu 145.4 34.0
Senior Gaomei Lv 147.3 34.0
Sophomore Peng Han 147.8 34.0
Senior Changli Lv 148.7 41.0
Sophomore Changjuan You 150.5 40.0#False代表升序排列
>>> print(df_data.sort_values('Height',ascending=False).head())Height Weight
Grade Name
Senior Xiaoqiang Qin 193.9 79.0Mei Sun 188.9 89.0Gaoli Zhao 186.5 83.0
Freshman Qiang Han 185.3 87.0
Senior Qiang Zheng 183.9 87.0
如果指定了两个列,那么在身高相同时,默认两列均使用升序排列。
2)索引排序
索引排序需要在列名前指定level,否则会报错:
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[['Grade', 'Name', 'Height', 'Weight']].set_index(['Grade','Name'])
#默认为True 代表升序排列
>>> print(df_data.sort_index(level=['Grade','Name'],ascending=[True,False]).head())Height Weight
Grade Name
Freshman Yanquan Wang 163.5 55.0Yanqiang Xu 152.4 38.0Yanqiang Feng 162.3 51.0Yanpeng Lv NaN 65.0Yanli Zhang 165.1 52.0
这里的升降序指得是字典顺序。
6.apply方法
apply()
用于用指定的自定义函数去计算该数据,如:
>>> def func(x):
>>> return x.max()
>>> df_data = pd.read_csv('./data/learn_pandas.csv')
>>> df_data = df_data[['Height','Weight']].head()
>>> print(df_data.apply(func))
Height 188.9
Weight 89.0
dtype: float64
当然此时可以利用拉姆达函数去省去def,但效果不如直接使用内置函数:
四、窗口对象
1.滑窗对象
以rolling()方法
为例举例说明:
>>> data = pd.Series(np.arange(10))
>>> roller = data.rolling(window=4)
>>> roller.mean()
0 NaN
1 NaN
2 NaN
3 1.5
4 2.5
5 3.5
6 4.5
7 5.5
8 6.5
9 7.5
dtype: float64
窗口默认为窗口的最后一个单元先放到数据的第一个位置,然后一直滑动到最后一个位置所组成的窗口位置集合,长度等于数据的长度,值为NaN的行有数据长度-1个。
滑动窗口支持传入自定义函数,如:
>>> data = pd.Series(np.arange(10))
>>> roller = data.rolling(window=4)
>>> print(roller.apply(lambda x:x.min()))
0 NaN
1 NaN
2 NaN
3 0.0
4 1.0
5 2.0
6 3.0
7 4.0
8 5.0
9 6.0
dtype: float64
shift()方法
表示将数据按指定方向移动,比如:
>>> data = pd.Series(np.arange(10))
>>> print(data)
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
dtype: int32
>>> data.shift(5)
0 NaN
1 NaN
2 NaN
3 NaN
4 NaN
5 0.0
6 1.0
7 2.0
8 3.0
9 4.0
dtype: float64
即表示数据向下移动5个单位,空缺用NaN补全,并且转换了dtype。如果实参为负数,则表示向下移动,移动距离为负数的绝对值。
diff()方法
表示数据的差值,如:
>>> data = pd.Series(np.arange(10))
>>> print(data)
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
dtype: int32
>>> print(data.diff(2))
0 NaN
1 NaN
2 2.0
3 2.0
4 2.0
5 2.0
6 2.0
7 2.0
8 2.0
9 2.0
dtype: float64
上面的例子表示,每个数据减去其上面2个位置的数据得到的如果,如果值不存在则填NaN。注意,如果不指明位置,默认为1,即上面相邻的数据。如果实参为负数,同上。
pct_change()方法
表示计算数据的相对增长率,如不输入实参,则默认将相邻的上方数据作为参考指标进行计算:
>>> data = pd.Series(np.arange(10))
>>> print(data)
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
dtype: int32
>>> print(data.pct_change())
0 NaN
1 inf
2 1.000000
3 0.500000
4 0.333333
5 0.250000
6 0.200000
7 0.166667
8 0.142857
9 0.125000
dtype: float64
练一练
题目:rolling对象的默认窗口方向都是向前的,某些情况下用户需要向后的窗口,例如对1,2,3设定向后窗口为2的sum操作,结果为3,5,NaN,此时应该如何实现向后的滑窗操作?
2.扩张窗口
>>> data = pd.Series(np.arange(10))
>>> print(data.expanding().mean())
0 0.0
1 0.5
2 1.0
3 1.5
4 2.0
5 2.5
6 3.0
7 3.5
8 4.0
9 4.5
dtype: float64
扩张窗口等价与把滑动窗口值为Nan的位置补全为0,相当于在左侧延长n-1长度的0(n为数据长度)
练一练
题目:cummax, cumsum, cumprod函数是典型的类扩张窗口函数,请使用expanding对象依次实现它们。
>>> s = pd.Series([1,3,6,10])
#cummax
>>> print(s.expanding().max())
0 1.0
1 3.0
2 6.0
3 10.0
dtype: float64
#cumsum
>>> print(s.expanding().sum())
0 1.0
1 4.0
2 10.0
3 20.0
dtype: float64
#cumprod
>>> print(s.expanding().apply(lambda x:x.prod()))
0 1.0
1 3.0
2 18.0
3 180.0
dtype: float64
前两个实现起来非常简单,最后一个值得思考,因为下面这种方式是不行的,只好用apply()
方法:
五、练习
Ex1:口袋妖怪数据集
>>> data = pd.read_csv('data/pokemon.csv')
>>> data.head()
1.验证Total值是否准确
>>> data = pd.read_csv('data/pokemon.csv')
>>> total = data['Total']
>>> data_six = data[data.columns[5:]]
>>> total_calc = data_six.sum(axis=1)
>>> print(np.all(total==total_calc))
True
思路:先把Total和其余6个属性值分堆处理,存放成Series数据,最后利用Numpy的all()方法
进行比较。
2.“#”属性重复的妖怪只保留第一条记录
>>> data = pd.read_csv('data/pokemon.csv')
>>> data = data.drop_duplicates('#',keep='first')
>>> print(data.shape)
(721, 11)
先删掉#重复的妖怪。
1)求第一属性的种类数量和前三多数量对应的种类
>>> Type1 = data['Type 1']
>>> print(Type1.nunique())
18
>>> print(Type1.value_counts()[:3].index.values)
['Water' 'Normal' 'Grass']
思路:利用nunique()方法
求出第一属性种类数量,利用value_counts()方法
求出数量降序排列的第一属性种类名,然后通过索引和index()方法
取得Index对象,最终通过.values
属性拿到List
类型的数据。
2)求第一属性和第二属性的组合种类
>>> res = data.drop_duplicates(['Type 1','Type 2'])
>>> print(res.shape)
(143, 11)
思路:利用drop_duplicates()方法
求出包含第一和第二属性无重复的组合种类。
3)求尚未出现过的属性组合
#返回指定列名的无重复数据列表
#ndarray格式 求得所有类别
>>> nd_types = data['Type 1'].unique()
>>> print(nd_types.shape)
(18,)
#已知总共18个属性 共18*18=324个组合种类 名称存放在名为total_types的list中
>>> total_types = [i+'_'+j if i!=j else i for i in nd_types for j in nd_types]
#获取现有的组合种类数据
>>> s_nowTypes = data.drop_duplicates(['Type 1','Type 2'])
>>> print(s_nowTypes.shape)
(143, 11)
>>> def func(x):
>>> if type(x[3])==str:
>>> return x[2]+'_'+x[3]
>>> else:
>>> return x[2]
#利用func函数,过滤无第二属性的妖怪,并存入now_types的Series数据类型中
>>> now_types = s_nowTypes.apply(func,axis=1)
#列表推导表达式获得结果
>>> res = [x for x in total_types if x not in now_types.values]
>>> print(len(res))
181
思路:
1)先求出所有属性,共18种,然后组合种类得到324种,注意两种一样的属性不要设成“x_x”,而直接叫x,将全部种类名存入名为total_types的List中。
2)然后根据上一题,找到当前已有的种类,此处利用apply()方法
并将axis设为1即为按行遍历,注意此时data的列长仍为11,所以需要判断第4列的type2是否为NaN,等价于判断否是为字符,如果是字符的话说明是双属性妖怪,返回拼接后的结果,否则只返回type1。将生成的数据存到名为now_types的Series中。
3)利用列表推导表达式判断尚未出现过的属性组合。
另外,分析一下对怪物数据的理解,首先假设data代表全部800条数据,其中存在重复‘#’值的情况,这代表不同怪物的不同形态,比如:
代欧奇希斯,拥有四个战斗形态分别是,一般形态、攻击形态、防御形态与速度形态,根据形态不同它的各项指标会大幅度变化。
我们通过data1 = data.drop_duplicates('#',keep='first')
只取同一个怪物的不同形态的第一种,data1中含有731条数据。然后通过data2 = data.drop_duplicates(['Type 1','Type 2'])
只取相同属性组合的第一种,data2中含有143条数据,代表目前存在的属性组合。因为怪物不存在两个属性均为一个的情况,所以单属性的怪物可以被双属性相同的怪物代表,即属性组合一共18*18=324种,减去data2的数据量,即324-143=181种。
更进一步,我们可以做出属性组合矩阵的可视化呈现:
#组合种类矩阵 行索引表示第一属性 列索引表示第二属性
>>> df_types = pd.DataFrame(index=types,columns=types)
#单次增加不同种类怪物数量
>>> def add(index1,index2):
>>> value = df_types[index1][index2]#如果当前统计值为NaN
>>> if type(value)==float:
>>> df_types[index1][index2] = 1
>>> else:
>>> df_types[index1][index2] += 1
>>> def func(x):#这里调换了i,j的顺序,为了适应df先列后行的访问次序 如df_types['Grass']['Fire']=0 访问的是'Grass'列'Fire'行#i为type2属性
>>> i = x[3]#j为type1属性
>>> j = x[2]#如果type2为nan
>>> if type(i)==float:#则将type2赋值为type1
>>> i = j#调用函数
>>> add(i,j)
>>> data = pd.read_csv('data/pokemon.csv')
>>> data = data.drop_duplicates('#',keep='first')
>>> data.apply(func,axis=1)
>>> print(df_types.count().sum())
143
>>> df_types
df_types存放的是不同属性组合,有了这个DataFrame数据,我们就可以轻易获得有关组合属性的相关数据,比如获得单属性怪物的数量:
#求矩阵的迹 实际意义为单属性怪物的数量
>>> df_types.values.trace()
371
3.按要求构造Series
a.生成包含物攻的Series并按要求替换
>>> data = pd.read_csv('data/pokemon.csv')
>>> data_attack = data['Attack']
>>> data_attack.mask((data_attack>=50) & (data_attack <=120),100,inplace=True)
>>> data_attack.mask((data_attack>120),150,inplace=True)
>>> data_attack.mask((data_attack<50),0,inplace=True)
>>> data_attack.replace({0:'low',100:'mid',150:'high'},inplace=True)
>>> print(data_attack)
0 low
1 mid
2 mid
3 mid
4 mid...
795 mid
796 high
797 mid
798 high
799 mid
Name: Attack, Length: 800, dtype: object
>>> print(data_attack.unique())
['low' 'mid' 'high']
思路:第一步用mask()方法
做一个等价替换,把三个区域的值都用一个数字表示;第二部用replace()方法
的映射替换。
如果省去第二部直接替换,在执行第二个mask()
的时候,会出现字符(mid)和数字进行比较,无法顺利实现,所以采用分阶段的方式。
b.生成包含第一属性的Series并转为大写字母
>>> data = pd.read_csv('data/pokemon.csv')
#方法一 直接转换
>>> data_type1 = data['Type 1']
>>> data_type1 = data['Type 1'].str.upper()
>>> print(data_type1)
#方法二 使用apply转换
>>> data_type1 = data['Type 1']
>>> data_type1 = data_type1.apply(lambda x:str.upper(x))
>>> print(data_type1)
#方法三 使用replace转换
>>> data_type1 = data['Type 1']
>>> replace_Dict = {x : str.upper(x) for x in data_type1.unique() }
>>> data_type1.replace(replace_Dict,inplace=True)
>>> print(data_type1)
0 GRASS
1 GRASS
2 GRASS
3 GRASS
4 FIRE...
795 ROCK
796 ROCK
797 PSYCHIC
798 PSYCHIC
799 FIRE
Name: Type 1, Length: 800, dtype: object
c.生成每个妖怪的离差并从大到小排序
#作用求离差
>>> def getDev(x):
>>> return ((x-x.median()).abs()).max()
>>> data = pd.read_csv('data/pokemon.csv')
#data表示所有能力值
>>> data = data[data.columns[-6:]]
>>> res = data.apply(getDev,axis=1).sort_values(ascending=False)
>>> print(res)
261 190.0
121 185.0
224 160.0
230 160.0
333 160.0...
175 -25.0
732 -27.0
446 -28.0
255 -30.0
206 -35.0
Length: 800, dtype: float64
思路:对整个DataFrame使用apply()方法
,其中自定义函数getDev()的作用是返回离差,然后对离差进行降序排列(ascending设为False)。
仔细解释一下getDev()函数
,首先在调用函数时,指定axis
为1,即按行运算,然后x代表着每行的数据,属于Series类型。因为Series依然可以进行向量化运算,所以x-x.median()
生成的是六项能力与中位数的差值的Series类型数据,然后利用max()方法
求得这个妖怪的离差并返回。
Ex2:指数加权窗口
1.expanding窗口实现ewm
#自定义方法
>>> def my_ewm(s,alpha):
>>> def func(x):
>>> s_deno = (1-alpha)**np.arange(x.size-1,-1,-1)
>>> return (s_deno*x).sum()/s_deno.sum()
>>> return s.expanding().apply(func)
>>> np.random.seed(20201219)
>>> s = pd.Series(np.random.randint(-1,2,30).cumsum())
>>> alpha = 0.2
#系统方法
>>> print(s.ewm(alpha=alpha).mean().head())
0 0.000000
1 0.555556
2 1.147541
3 1.436314
4 1.603998
dtype: float64
#自定义方法
>>> print(my_ewm(s,alpha).head())
0 0.000000
1 0.555556
2 1.147541
3 1.436314
4 1.603998
dtype: float64
思路:从expanding()方法
入手,对于每一个扩张窗口,第一个窗口值的(1-α)的次数为0,然后依次递增,且数目和窗口宽度相同,反过来说,由加权归一化公式可知窗口的第一个值是与最大幂数的(1-α)相乘,因此考虑先建立一个ndarray数组用于存储倒序的幂数数组,首项为窗口大小-1,长度为窗口大小。然后利用Numpy的向量化性质进行公式复现即可。
注意np.arange(10,-1,-1)
生成的是以10开始,0结束的递减ndarray数组。
2.给定限制窗口n,给出公式及实现
新的权重没有发生变化,但是 y t y{_t} yt的公式中求和公式的上限变为n-1:
区别在于限制了窗口大小,所以我们在上一题设计的幂数数组的长度也会固定,实现方法为:
#自定义方法
>>> def my_new_ewm(s,alpha,n):
>>> def func(x):
>>> return (s_deno*x).sum()/s_deno.sum()
>>> s_deno = (1-alpha)**np.arange(n-1,-1,-1)
>>> return s.rolling(window=n).apply(func)
>>> np.random.seed(20201219)
>>> s = pd.Series(np.random.randint(-1,2,30).cumsum())
>>> alpha = 0.2
>>> n=4
#自定义方法
>>> print(my_new_ewm(s,alpha,n))
0 NaN
1 NaN
2 NaN
3 1.436314
4 1.826558
dtype: float64
实现方法的改动在于函数参数新增了一个窗口宽度n,另外由于窗口保持固定,所以s_deno
数组只需生成一次即可,自定义函数func()
没有本质的变化。
参考文献
#pandas DataFrame的修改方法
1.https://www.cnblogs.com/datasnail/p/9787808.html
#pandas官方API
2.https://pandas.pydata.org/pandas-docs/stable