Python数据分析—Dataframe

Python

# Pandas_Dataframe

# 一、DataFrame简介

相比Series,由多个Series组成的DataFrame,才是我们分析数据是最常打交道的数据结构。

DataFrame看起来像是一个表格,它的不同列可以是不同数据类型,而不是像NumPy二维数组那样,要求数据类型全部保持一致。

相比起Series,DataFrame每个值不止有索引,也有列名。换个角度来看,DataFrame就像是由Series组成的字典,每个Series对应一个键名,也就是列名。

# 二、创建DataFrame

创建DataFrame时,会自动进行索引对齐

# (一)参数传入一个字典,值是Series

既然DataFrame可以看成由Series组成的字典,那么第一个创建方法就是,参数传入一个字典。键是各个Series所对应的列名。

创建好后,Jupyter Notebook会输出一个,排版好看的DataFrame表格。索引就对应了Series的索引,而列名对应我们传入的键。

参数传入一个字典,值是Series

import pandas as pd
s_id = pd.Series(["01", "02", "03", "04", "05"])
s_class = pd.Series(["二班", "一班", "二班", "三班", "一班"])
s_grade = pd.Series([92, 67, 70, 88, 76])

df1 = pd.DataFrame({"学号": s_id, "班级": s_class, "成绩": s_grade})
df1
1
2
3
4
5
6
7

运行结果

学号 班级 成绩
0 01 二班 92
1 02 一班 67
2 03 二班 70
3 04 三班 88
4 05 一班 76

# (二)参数传入一个字典,值是列表

l_id = ["01", "02", "03", "04", "05"]
l_class = ["二班", "一班", "二班", "三班", "一班"]
l_grade = [92, 67, 70, 88, 76]

df2 = pd.DataFrame({"学号": s_id, "班级": s_class, "成绩": s_grade})
df2
1
2
3
4
5
6

运行结果

学号 班级 成绩
0 01 二班 92
1 02 一班 67
2 03 二班 70
3 04 三班 88
4 05 一班 76

Dataframe默认的索引和Series一样,都是从0开始依次递增的整数,来表示位置。但如果传入的Series有标签索引的话,DataFrame的索引也会变成相应的标签。

s_id = pd.Series(["01", "02", "03", "04", "05"], index=["小明", "小红", "小杰", "小丽", "小华"])
s_class = pd.Series(["二班", "一班", "二班", "三班", "一班"], index=["小明", "小红", "小杰", "小丽", "小华"])
s_grade = pd.Series([92, 67, 70, 88, 76],
                    index=["小明", "小红", "小杰", "小丽", "小华"])

df3 = pd.DataFrame({"学号": s_id, "班级": s_class, "成绩": s_grade})
df3
1
2
3
4
5
6
7

运行结果

学号 班级 成绩
小明 01 二班 92
小红 02 一班 67
小杰 03 二班 70
小丽 04 三班 88
小华 05 一班 76

# (三)参数传入一个嵌套字典

参数传入一个嵌套字典,可以一次性创建出既有标签索引,也有列名的DataFrame

最外层的键仍然对应各个列名,而值则对应每列的Series;里层字典的键对应Series的标签索引

df4 = pd.DataFrame({"学号": {"小明": "01", "小红": "02", "小杰": "03", "小丽": "04", "小华": "05"}, 
                    "班级": {"小明": "二班", "小红": "一班", "小杰": "二班", "小丽": "三班", "小华": "一班"}, 
                    "成绩": {"小明": 92, "小红": 67, "小杰": 70, "小丽": 88, "小华": 76}})
df4
1
2
3
4

运行结果

学号 班级 成绩
小明 01 二班 92
小红 02 一班 67
小杰 03 二班 70
小丽 04 三班 88
小华 05 一班 76

# 三、DataFrame的常用属性

# (一)index属性与columns属性

要获得DataFrame的索引,可以用index属性;要获取所有列名,可以用columns属性。

返回的索引和列名的数据类型,都是pandas库的Index类

df4.index

Index(['小明', '小红', '小杰', '小丽', '小华'], dtype='object')

df4.columns

Index(['学号', '班级', '成绩'], dtype='object')
1
2
3
4
5
6
7

# (二)values属性

要获取所有的值,可以用values属性

返回的类型是NumPy数组,那所有针对NumPy数组的操作,都可以用在values属性上了

df4.values

array([['01', '二班', 92],
       ['02', '一班', 67],
       ['03', '二班', 70],
       ['04', '三班', 88],
       ['05', '一班', 76]], dtype=object)
1
2
3
4
5
6
7

# (三)T属性

对DataFrame进行转置,可以用T属性,大写的T。

返回的结果会把行和列进行转置。

df4.T
1

运行结果

小明 小红 小杰 小丽 小华
学号 01 02 03 04 05
班级 二班 一班 二班 三班 一班
成绩 92 67 70 88 76

获取转置后的属性

df5 = df4.T
print(df5.index)
print(df5.columns)
print(df5.values)
1
2
3
4

运行结果

Index(['学号', '班级', '成绩'], dtype='object')
Index(['小明', '小红', '小杰', '小丽', '小华'], dtype='object')
[['01' '02' '03' '04' '05']
 ['二班' '一班' '二班' '三班' '一班']
 [92 67 70 88 76]]
1
2
3
4
5

转置后,索引、列名、值都相应发生了变化

# 四、从DataFrame中提取数据

# (一)提取DataFrame的列

# 1、提取某个列

1)在DataFrame后面跟上方括号,里面放上列名,就能把列名对应的列提取出来。

列的类型是Series,索引对应DataFrame原本的索引。

这就跟我们在Python字典后面跟上方括号,里面放上键名,就能把键对应的值提取出来是类似的

df4['成绩']

小明    92
小红    67
小杰    70
小丽    88
小华    76
Name: 成绩, dtype: int64
1
2
3
4
5
6
7
8
df4['班级']

小明    二班
小红    一班
小杰    二班
小丽    三班
小华    一班
Name: 班级, dtype: object
1
2
3
4
5
6
7
8

2)也可以通过".列名" ,来获取对应的列,因为每列Series其实也是DataFrame的属性

如果列名里面有空格或特殊符号的话,就不能通过属性名来获取了,只能通过方括号

Pandas不允许通过属性来添加/更新列

df4.成绩

小明    92
小红    67
小杰    70
小丽    88
小华    76
Name: 成绩, dtype: int64
1
2
3
4
5
6
7
8
df4.班级

小明    二班
小红    一班
小杰    二班
小丽    三班
小华    一班
Name: 班级, dtype: object
1
2
3
4
5
6
7
8

# 2、提取任意多列

可以在方括号里放入列表,再在列表里面传入多个列名。

这样做返回的就不是Series,而是DataFrame了

df4[["成绩", "班级"]]
1

运行结果

成绩 班级
小明 92 二班
小红 67 一班
小杰 70 二班
小丽 88 三班
小华 76 一班

# (二)提取DataFrame的行

提取列用列名,提取行就应该用索引

# 1、提取某个行

每行数据是以Series类型进行返回的

1)loc 按照标签索引提取行

2)iloc 按照位置索引提取行

df4.loc["小丽"]

学号    04
班级    三班
成绩    88
Name: 小丽, dtype: object
1
2
3
4
5
6
df4.iloc[3]

学号    04
班级    三班
成绩    88
Name: 小丽, dtype: object
1
2
3
4
5
6

# 2、提取部分行

与Series切片类似,给loc标签索引范围,或者给iloc位置索引范围,可以获得多行数据

标签索引做切片是包含结束值的

df4.loc["小红": "小丽"]
1

运行结果

学号 班级 成绩
小红 02 一班 67
小杰 03 二班 70
小丽 04 三班 88
 df4.iloc[1: 3]
1

运行结果

学号 班级 成绩
小红 02 一班 67
小杰 03 二班 70

# 3、提取任意多行

可以给loc或iloc后面的方括号里,放入一个列表,里面是想提取出的行的标签或位置索引。与之前提取任意列类似。

df4.loc[["小丽", "小红"]]
1

运行结果

学号 班级 成绩
小丽 04 三班 88
小红 02 一班 67
df4.iloc[[3, 1]]
1

运行结果

学号 班级 成绩
小丽 04 三班 88
小红 02 一班 67

# (三)提取DataFrame的值

# 1、提取某个DataFrame的元素

可以在loc或iloc后面的方括号里,放上2个参数,第一个表示行,第二个表示列,就能提取出表格某个位置上的值

 df4.loc["小杰", "学号"]

'03'

df4.iloc[2, 0]

'03'
1
2
3
4
5
6
7

# 2、提取部分DataFrame

提取部分表格数据类似,只需要在loc或iloc后面的方括号里放上2个参数,第一个表示行的切片,第二个表示列的切片,就可以把表格的一部分给切出来

标签索引做切片会包含结束值

df4.loc["小红": "小丽", "学号": "成绩"]
1

运行结果

学号 班级 成绩
小红 02 一班 67
小杰 03 二班 70
小丽 04 三班 88
df4.iloc[1: 3, 0: 2]
1

运行结果

学号 班级
小红 02 一班
小杰 03 二班

# 3、提取部分列,同时保留所有行;提取部分行,同时保留所有列

一个省事的方法是,省略希望保留所有的切片里冒号前后的值,直接放上一个冒号,这能默认表示全部范围的索引

df4.loc[:, "班级": "成绩"]
1

运行结果

班级 成绩
小明 二班 92
小红 一班 67
小杰 二班 70
小丽 三班 88
小华 一班 76
df4.iloc[0: 3, :]
1

运行结果

学号 班级 成绩
小明 01 二班 92
小红 02 一班 67
小杰 03 二班 70

# 4、提取不相邻的行或列

通过往loc或iloc后面的方括号里放入列表,通过标签或位置指明提取哪些

df4.loc[["小红", "小丽"], "学号": "班级"]
1

运行结果

学号 班级
小红 02 一班
小丽 04 三班
df4.iloc[[1, 3], 0: 2]
1

运行结果

学号 班级
小红 02 一班
小丽 04 三班

# 五、根据条件筛选DataFrame中的行

# (一)根据条件筛选行,相比于根据条件筛选列来说,是更加常见和合理的。

因为一般每一行代表一个实例,比如一个城市、一个学生,而每一列代表数据实例的属性,比如说城市的人口、学生的身高。那我们筛选符合条件的行,就相当于从已有数据里,提取符合条件的实例,比如人口在1000万以上的城市,身高在1.6米以上的学生。

# (二)语法

和NumPy的数组以及Pandas的Series是很类似的,在DataFrame后面跟一个方括号,里面放上针对列的条件。列的条件包括:数据类型是Series的列和条件。返回列的两种方法:通过"["列名"]",或者通过".属性名",都可以应用在列的条件中。

# (三)原理

DataFrame的列是Series类型,而Series和条件结合起来,会返回一个布尔值组成的Series,它的长度和DataFrame的行数相对应。DataFrame会用布尔值的Series进行索引,保留True所对应的索引的行。

df4['成绩'] > 80

小明     True
小红    False
小杰    False
小丽     True
小华    False
Name: 成绩, dtype: bool
1
2
3
4
5
6
7
8
df4[df4["成绩"] > 80]
1

运行结果

学号 班级 成绩
小明 01 二班 92
小丽 04 三班 88
df4[df4.成绩 > 80]
1

运行结果

学号 班级 成绩
小明 01 二班 92
小丽 04 三班 88

# (四)结合逻辑运算

条件也可以结合逻辑运算,因为DataFrame有不同列,所以不同条件里,可以根据不同列的变量进行筛选。

df4[(df4.成绩 > 80) & (df4["班级"] == "三班")]
1

运行结果

学号 班级 成绩
小丽 04 三班 88

# 六、head方法和tail方法

基本上,对DataFrame的操作方法,都是默认不改变原始DataFrame的,而是返回一个新的DataFrame

要操作生效的话,要么就得进行重新赋值,要么指定可选参数inplace=True

# (一)head方法

1、DataFrame.head(num)会返回给我们DataFrame前num行的内容,num是可选参数,默认是前5行内容。

2、用处:当我们和实际数据打交道的时候,可能动辄几千、几万甚至几十万数据,这种时候,这个方法就很实用了。可以看一眼开头几行,快速了解数据包含的信息,以及各列里面变量的特点。

df4.head()
1

运行结果

学号 班级 成绩
小明 01 二班 92
小红 02 一班 67
小杰 03 二班 70
小丽 04 三班 88
小华 05 一班 76

增加指定行数

df4.head(2)
1

运行结果

学号 班级 成绩
小明 01 二班 92
小红 02 一班 67

# (二)tail方法

DataFrame.tail(num)会返回给我们DataFrame后num行的内容,num是可选参数,默认是后5行内容

df4.tail(2)
1

运行结果

学号 班级 成绩
小丽 04 三班 88
小华 05 一班 76

# (三)sample方法

DataFrame.sample(num)会返回给我们DataFrame随机num行的内容,num是可选参数,默认是随机1行内容

df4.sample()
1

运行结果

学号 班级 成绩
小华 05 一班 76

# 七、更新/增加DataFrame的一列值

更新或者增加,取决于列名是否已经存在

import pandas as pd
name = pd.Series(["小陈", "小李", "小王", "小张", "小赵", "小周"], index=["001", "002", "003", "004", "005", "006"])
gender = pd.Series(["女", "女", "男", "男", "女", "男"], index=["006", "005", "004", "003", "002", "001"])
height = pd.Series([172.5, 168.0, 178.2, 181.3, 161.7, 159.8], index=["001", "002", "003", "004", "005", "006"])
grade = pd.Series([89, 92, 82, 96, 93, 84], index=["001", "002", "003", "004", "005", "006"])
df1 = pd.DataFrame({"姓名": name, "性别": gender, "身高": height, "成绩": grade})
df1
1
2
3
4
5
6
7
姓名 性别 身高 成绩
001 小陈 172.5 89
002 小李 168.0 92
003 小王 178.2 82
004 小张 181.3 96
005 小赵 161.7 93
006 小周 159.8 84

# (一)、Series处理列值

dataframe['列名'] = Series
1

如果DataFrame有标签索引,那被赋值的Series也要有index属性,才能成功和DataFrame的索引对齐,否则所有标签无法对齐的地方,都会产生缺失值

df1['成绩'] = pd.Series([90, 91, 83, 95, 94, 85])
df1
1
2
姓名 性别 身高 成绩
001 小陈 172.5 NaN
002 小李 168.0 NaN
003 小王 178.2 NaN
004 小张 181.3 NaN
005 小赵 161.7 NaN
006 小周 159.8 NaN
df1['成绩'] = pd.Series([90, 91, 83, 95, 94, 85], index=['001', '002', '003', '004', '005', '006'])
df1
1
2
姓名 性别 身高 成绩
001 小陈 172.5 90
002 小李 168.0 91
003 小王 178.2 83
004 小张 181.3 95
005 小赵 161.7 94
006 小周 159.8 85

# (二)、列表处理列值

dataframe['列名'] = 列表
1

用列表的好处是不需要指明标签,会自动按顺序对齐,列表的长度和DataFrame本身的行数温和就可以

df1['成绩'] = [90, 91, 83, 95, 94, 85]
df1
1
2
姓名 性别 身高 成绩
001 小陈 172.5 90
002 小李 168.0 91
003 小王 178.2 83
004 小张 181.3 95
005 小赵 161.7 94
006 小周 159.8 85
# 增加DataFrame的一列值
df1["班级"] = ["一班", "三班", "二班", "三班", "一班", "二班"]
df1
1
2
3
姓名 性别 身高 成绩 班级
001 小陈 172.5 90 一班
002 小李 168.0 91 三班
003 小王 178.2 83 二班
004 小张 181.3 95 三班
005 小赵 161.7 94 一班
006 小周 159.8 85 二班

# 八、更新/增加DataFrame的一行值

更新或者增加,取决于列名是否已经存在

既然可以通过loc或iloc提取出一行数据,那就可以以同样的方式更新那行

# (一)、Series处理行值

dataframe.loc/iloc['索引'] = Series
1

需要把Series的标签与列名进行对应,否则会产生缺失值

df1.loc["005"] = pd.Series(["小赵", "女", 162.7, 95, "一班"],
                           index=["姓名", "性别", "身高", 
                                  "成绩", "班级"])
df1
1
2
3
4
姓名 性别 身高 成绩 班级
001 小陈 172.5 90 一班
002 小李 168.0 91 三班
003 小王 178.2 83 二班
004 小张 181.3 95 三班
005 小赵 162.7 95 一班
006 小周 159.8 85 二班

# (二)、列表处理行值

dataframe.loc/iloc['索引'] = 列表
1

不需要指明标签,会自动按顺序对齐

df1.loc["005"] = ["小赵", "女", 162.7, 95, "一班"]
df1
1
2
姓名 性别 身高 成绩 班级
001 小陈 172.5 90 一班
002 小李 168.0 91 三班
003 小王 178.2 83 二班
004 小张 181.3 95 三班
005 小赵 162.7 95 一班
006 小周 159.8 85 二班

添加行,只能用loc。用iloc,会报错,因为给iloc里面的位置索引得是存在的

# 增加DataFrame的一行值
df1.loc["007"] = ["小孙", "男", 182.7, 71, "一班"]
df1
1
2
3
姓名 性别 身高 成绩 班级
001 小陈 172.5 90 一班
002 小李 168.0 91 三班
003 小王 178.2 83 二班
004 小张 181.3 95 三班
005 小赵 162.7 95 一班
006 小周 159.8 85 二班
007 小孙 182.7 71 一班

# 九、删除DataFrame的行或列

用drop函数

drop函数只会返回删除后的DataFrame,但不会改变原始的DataFrame。假如让原本DataFrame的行被删除,可以把drop返回的结果赋值给原本的DataFrame,来实现更新。

# (一)、删除DataFrame的行

# 1、删除DataFrame的单行

dataframe.drop('标签索引')
df1.drop('003')
1
2
姓名 性别 身高 成绩 班级
001 小陈 172.5 90 一班
002 小李 168.0 91 三班
004 小张 181.3 95 三班
005 小赵 162.7 95 一班
006 小周 159.8 85 二班
007 小孙 182.7 71 一班

# 2、删除DataFrame的任意多行

dataframe.drop(['标签索引1', '标签索引2'...])
df1.drop(['003', '007'])
1
2
姓名 性别 身高 成绩 班级
001 小陈 172.5 90 一班
002 小李 168.0 91 三班
004 小张 181.3 95 三班
005 小赵 162.7 95 一班
006 小周 159.8 85 二班

# (二)、删除DataFrame的列

# 1、删除DataFrame的单列

dataframe.drop('列名', axis=1)
1

传入可选参数'axis=1'

因为DataFrame是二维的,可以看成是有两个轴线,axis=0表示沿着索引纵向进行操作,axis=1,表示沿着列名横向进行操作。一般针对DataFrame的方法,默认axis为0,因为沿着索引纵向进行操作的时候,数据的变量含义和类型一般是相同的,比如都是表示身高的浮点数,所以适合进行统计操作。drop,默认axis也是0,那沿着索引纵向操作,删除的就是行,当我们指明axis为1时,是沿着列名横向进行操作,所以删除的就是列。正是因为DataFrame二维的特征,针对很多操作我们都可以指明axis到底是0还是1。

df1.drop('身高', axis=1)
1
姓名 性别 成绩 班级
001 小陈 90 一班
002 小李 91 三班
003 小王 83 二班
004 小张 95 三班
005 小赵 95 一班
006 小周 85 二班
007 小孙 71 一班

# 2、删除DataFrame的任意多列

dataframe.drop(['列名1', '列名2'...], axis=1)
df1.drop(['身高', '性别'], axis=1)
1
2
姓名 成绩 班级
001 小陈 90 一班
002 小李 91 三班
003 小王 83 二班
004 小张 95 三班
005 小赵 95 一班
006 小周 85 二班
007 小孙 71 一班

# (三)、根据条件删除行或列

  1. 可以反向操作,把不符合条件的行或列筛选出来,重新赋值给原始DataFrame
# 删除成绩小于75的同学的档案
df1 = df1[df1.成绩 > 75]
df1
1
2
3
姓名 性别 身高 成绩 班级
001 小陈 172.5 90 一班
002 小李 168.0 91 三班
003 小王 178.2 83 二班
004 小张 181.3 95 三班
005 小赵 162.7 95 一班
006 小周 159.8 85 二班
  1. 正向操作。由于drop函数中传入'标签索引'作为参数。所以我们先根据条件筛选出要删除的行,再用.index获取作为属性的标签索引,最后作为参数放入DataFrame的drop函数中。
# 删除身高大于180的同学的档案
 df1.drop(df1[df1.身高 > 180].index, inplace=True)
 df1
1
2
3
C:\Users\stube\AppData\Local\Temp\ipykernel_26868\3609063738.py:2: SettingWithCopyWarning: 
 A value is trying to be set on a copy of a slice from a DataFrame
 
 See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
   df1.drop(df1[df1.身高 > 180].index, inplace=True)
1
2
3
4
5
.dataframe tbody tr th {
     vertical-align: top;
 }
 
 .dataframe thead th {
     text-align: right;
 }
1
2
3
4
5
6
7
姓名 性别 身高 成绩 班级
001 小陈 172.5 90 一班
002 小李 168.0 91 三班
003 小王 178.2 83 二班
005 小赵 162.7 95 一班
006 小周 159.8 85 二班

# 十、DataFrame和DataFrame之间的运算

与Series与Series之间的运算类似。

DataFrame和DataFrame之间的运算,索引和索引、列名和列名会自动对齐。

# 1、使用符号运算

准备df1数据

df1 = pd.DataFrame({"a": {"001": 0, "003": 4, "005": 8, "007": 12},
                    "b": {"001": 1, "003": 5, "005": 9, "007": 13},
                    "c": {"001": 2, "003": 6, "005": 10, "007": 14},
                    "d": {"001": 3, "003": 7, "005": 11, "007": 15}})
df1
1
2
3
4
5
a b c d
001 0 1 2 3
003 4 5 6 7
005 8 9 10 11
007 12 13 14 15

准备df2数据

df2 = pd.DataFrame({"a": [0, 3, 6],
                    "b": [1, 4, 7],
                    "c": [2, 5, 8]})
df2
1
2
3
4
a b c
0 0 1 2
1 3 4 5
2 6 7 8

相加

df1 + df2
1
a b c d
001 NaN NaN NaN NaN
003 NaN NaN NaN NaN
005 NaN NaN NaN NaN
007 NaN NaN NaN NaN
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN

分析:df1与df2是有重合的列名的,a, b, c这三列是可以对齐上,而d列会全是缺失值;但除了列,行也要对齐,df1的标签是001、003、005、007,而df2的标签是0、1、2,没有一个对得上。可以看到,计算结果会保留双方全部索引和列名,所以每一个计算结果都是NaN

假如一个DataFrame没有标签,另一个DataFrame有标签,两个DataFrame之间运算是会拿标签索引和位置索引对齐,不一样则会保留全部索引

df2 = pd.DataFrame({"a": {"001": 0, "003": 3, "006": 6},
                    "b": {"001": 1, "003": 4, "006": 7},
                    "c": {"001": 2, "003": 5, "006": 8}})
df2
1
2
3
4
a b c
001 0 1 2
003 3 4 5
006 6 7 8
df1 + df2
1
a b c d
001 0.0 2.0 4.0 NaN
003 7.0 9.0 11.0 NaN
005 NaN NaN NaN NaN
006 NaN NaN NaN NaN
007 NaN NaN NaN NaN

分析:有一部分数据对齐成功,不全是缺失值了

# 2、用方法而不是符号运算

如果你希望给缺失的值一个默认值的话,可以给fill_value这个参数传一个值

df1.add(df2, fill_value=0)
1
a b c d
001 0.0 2.0 4.0 3.0
003 7.0 9.0 11.0 7.0
005 8.0 9.0 10.0 11.0
006 6.0 7.0 8.0 NaN
007 12.0 13.0 14.0 15.0

分析:索引006、列名d的值不存在,是由于这个位置的值,在两个DataFrame中都不存在。因此没有相加操作,也不存在替换。但因为结果会保留所有索引和列名,导致那个位置会有个占位的NaN值。

df1.sub(df2, fill_value=0)
1
a b c d
001 0.0 0.0 0.0 3.0
003 1.0 1.0 1.0 7.0
005 8.0 9.0 10.0 11.0
006 -6.0 -7.0 -8.0 NaN
007 12.0 13.0 14.0 15.0
df1.mul(df2, fill_value=0)
1
a b c d
001 0.0 1.0 4.0 0.0
003 12.0 20.0 30.0 0.0
005 0.0 0.0 0.0 0.0
006 0.0 0.0 0.0 NaN
007 0.0 0.0 0.0 0.0
df1.div(df2, fill_value=0)
1
a b c d
001 NaN 1.00 1.0 inf
003 1.333333 1.25 1.2 inf
005 inf inf inf inf
006 0.000000 0.00 0.0 NaN
007 inf inf inf inf

# 十一、DataFrame和Series之间运算

DataFrame和Series之间运算同样会自动对齐,是用Series的索引和DataFrame的列名对齐,然后把操作运用在DataFrame的每一行上,对齐不了的就是NaN。

比如:

对齐的情况

s1 = pd.Series([0.1, 0.2, 0.3, 0.4, 0.5], index=["a", "b", "c", "d", "e"])
s1
1
2

运行结果

a    0.1
b    0.2
c    0.3
d    0.4
e    0.5
dtype: float64
1
2
3
4
5
6

不对齐的情况

s1 * df1
1

运行结果

a b c d e
001 0.0 0.2 0.6 1.2 NaN
003 0.4 1.0 1.8 2.8 NaN
005 0.8 1.8 3.0 4.4 NaN
007 1.2 2.6 4.2 6.0 NaN

这里s1里索引a对应的值,会与df1里a列下面的所有值进行相乘;索引b对应的值,会与df1里b列下面的所有值进行相乘。

广播机制,Series和DataFrame之间的操作也是广播机制的体现,因为广播机制值得就是不同维度数据之间的运算机制。

# 十二、DataFrame和单个数字之间运算

name = pd.Series(["小陈", "小李", "小王", "小张", "小赵", "小周"], index=["001", "002", "003", "004", "005", "006"])
gender = pd.Series(["女", "女", "男", "男", "女", "男"], index=["006", "005", "004", "003", "002", "001"])
height = pd.Series([172.5, 168.0, 178.2, 181.3, 161.7], index=["001", "002", "003", "004", "005"])
students = pd.DataFrame({"姓名": name, "性别": gender, "身高": height})
students
1
2
3
4
5
姓名 性别 身高
001 小陈 172.5
002 小李 168.0
003 小王 178.2
004 小张 181.3
005 小赵 161.7
006 小周 NaN

# 1、运算运用到DataFrame的每一项数据上

运算会被运用到DataFrame的每一项数据上,所以要确保操作确实能被运用到里面的所有数据类型上。因为DataFrame包含多种数据类型,同一个操作放到不同类型上可能会有不同效果,也可能会报错

students * 5
1
姓名 性别 身高
001 小陈小陈小陈小陈小陈 男男男男男 862.5
002 小李小李小李小李小李 女女女女女 840.0
003 小王小王小王小王小王 男男男男男 891.0
004 小张小张小张小张小张 男男男男男 906.5
005 小赵小赵小赵小赵小赵 女女女女女 808.5
006 小周小周小周小周小周 女女女女女 NaN

# 2、先提取出需要的DataFrame数据,再进行操作

也可以先提取出需要的DataFrame数据,用数据类型一致的那一部分,再拿去进行操作

students[['姓名', '性别']] * 5
1
姓名 性别
001 小陈小陈小陈小陈小陈 男男男男男
002 小李小李小李小李小李 女女女女女
003 小王小王小王小王小王 男男男男男
004 小张小张小张小张小张 男男男男男
005 小赵小赵小赵小赵小赵 女女女女女
006 小周小周小周小周小周 女女女女女

# 十三、聚合运算

# (一)、统计方法

NumPy数组和Pandas的Series的统计方法,包括max, min, sum, mean,在DataFrame中也有相同名字的方法

不同之处在于,由于DataFrame是二维的,我们可以指定是沿着索引纵向操作,还是沿着列名横向操作。

import pandas as pd
player1 = pd.Series([8.5, 7.9, 8.2, 7.6, 8.8, 7.4], index=["001", "002", "003", "004", "005", "006"])
player2 = pd.Series([9.0, 8.3, 8.6, 7.7, 8.9, 7.8], index=["001", "002", "003", "004", "005", "006"])
player3 = pd.Series([8.7, 8.1, 8.4, 7.9, 8.6, 7.3], index=["001", "002", "003", "004", "005", "006"])
df1 = pd.DataFrame({"选手1": player1, "选手2": player2, "选手3": player3})
df1
1
2
3
4
5
6
选手1 选手2 选手3
001 8.5 9.0 8.7
002 7.9 8.3 8.1
003 8.2 8.6 8.4
004 7.6 7.7 7.9
005 8.8 8.9 8.6
006 7.4 7.8 7.3
df1.mean()

选手1    8.066667
选手2    8.383333
选手3    8.166667
dtype: float64
1
2
3
4
5
6

默认沿着索引纵向操作,计算各列的统计值,因此返回结果的Series索引和列名对应

df1.mean(axis=1)

001    8.733333
002    8.100000
003    8.400000
004    7.733333
005    8.766667
006    7.500000
dtype: float64
1
2
3
4
5
6
7
8
9

如果用可选参数指定axis=1的话,就变成横向操作,计算各行的统计值,因此返回结果的Series索引和DataFrame索引对应。

# (二)、describe方法

与Series的describe方法类似。用在DataFrame上,能得到各列的统计信息

如果DataFrame既有数字列也有非数字列,我们不需要吧数字列提取出来再调用这个方法,describe方法会自动忽略掉所有非数字列,只计算数字列的统计信息

如果DataFrame数据类型全都都是object,那么describe方法会展现针对object类型数据的信息

df1["姓名"] = ["小陈", "小李", "小王", "小张", "小赵", "小周"]
df1
1
2
选手1 选手2 选手3 姓名
001 8.5 9.0 8.7 小陈
002 7.9 8.3 8.1 小李
003 8.2 8.6 8.4 小王
004 7.6 7.7 7.9 小张
005 8.8 8.9 8.6 小赵
006 7.4 7.8 7.3 小周
df1.describe()
1
选手1 选手2 选手3
count 6.000000 6.000000 6.000000
mean 8.066667 8.383333 8.166667
std 0.535413 0.549242 0.520256
min 7.400000 7.700000 7.300000
25% 7.675000 7.925000 7.950000
50% 8.050000 8.450000 8.250000
75% 8.425000 8.825000 8.550000
max 8.800000 9.000000 8.700000

# 十四、apply方法

与Series的apply方法类似,DataFrame中也有相同名字的方法。

参数接受一个函数,调用后把DataFrame的每列或每行的Series数据,分别作为那个函数的参数,默认对列进行操作。返回的Series里的元素,就是那个函数对原始Series里各列或各行调用后的结果。

apply方法不改变原始DataFrame,而是会返回一个新的DataFrame

# 写出一个函数,去掉选手最高分和最低分后,求平均分
def trim_mean(data):
    data_len = len(data)
    data_sum = data.sum()
    max_num = data.max()
    min_num = data.min()
    return (data_sum - max_num - min_num) / data_len
1
2
3
4
5
6
7
df1.apply(trim_mean)

选手1    5.366667
选手2    5.600000
选手3    5.500000
dtype: float64
1
2
3
4
5
6
df1.apply(trim_mean, axis=1)

001    2.900000
002    2.700000
003    2.800000
004    2.566667
005    2.933333
006    2.466667
dtype: float64
1
2
3
4
5
6
7
8
9

# 十五、applymap方法

与apply方法区别在于,传给apply的函数,会运用在每列或每行上;而传给applymap的函数,会运用在每一个元素上

applymap方法不改变原始DataFrame,而是会返回一个新的DataFrame

df1.applymap(lambda x: x + 5)
1
选手1 选手2 选手3
001 13.5 14.0 13.7
002 12.9 13.3 13.1
003 13.2 13.6 13.4
004 12.6 12.7 12.9
005 13.8 13.9 13.6
006 12.4 12.8 12.3
最近修改于: 2024/12/11 00:00:05
和宇宙温柔的关联
房东的猫