目录
4.1 索引操作
4.1.1 建立索引
4.1.2 重置索引
4.1.3 索引类型
4.1.4 索引对象
4.1.5 索引的属性
4.1.6 索引的操作
4.1.7 索引重命名
4.1.8 修改索引内容
4.2 数据的信息
4.2.1 查看样本
4.2.2 数据形状
4.2.3 基础信息
4.2.4 数据类型
4.2.5 行列索引内容
4.2.6 其他信息
4.3 统计计算
4.3.1 描述统计
4.3.2 数学统计
4.3.3 统计函数
4.3.4 非统计计算
本章将介绍Pandas对数据的基础操作,包括索引的创建和使用、数据信息的查看、数据的筛选、数据的统计、数据类型的转换、排序、添加修改、添加修改数据、使用函数等内容。这些是最为常见的操作,几乎所有数据分析工作都会涉及。
建立索引可以在数据读取加载中指定索引:
data = 'https://www.gairuo.com/file/data/dataset/team.xlsx'
df = pd.read_excel(data, index_col='name') # 将索引设置为name
df
'''
team Q1 Q2 Q3 Q4
name
Liver E 89 21 24 64
Arry C 36 37 37 57
Ack A 57 60 18 84
Eorge C 93 96 71 78
Oah D 65 49 61 86
...
'''
我们发现name成为了索引,在显示时已经不与其他列名在一行了,而是自己单独占有一行。如果加载时没有指定索引,我们可以使用df.set_index()指定:
df = pd.read_excel(data) # 读取数据不设索引
df.set_index('name') # 设置索引
如果需要,我们还可以设置两层索引:
df.set_index(['name', 'team']) # 设置两层索引
df.set_index([df.name.str[0],'name']) # 将姓名的第一个字母和姓名设置为索引
需要注意的是,在以上操作中,我们并没有修改原来的df变量中的内容,如果希望用设置索引后的数据替换原来df变量中的数据,可以直接进行赋值操作或者传入inplace参数:
df = df.set_index('name') # 建立索引并重写覆盖df
df.set_index('name', inplace=True) # 同上,使索引生效
可以将一个Series指定为索引:
s = pd.Series([i for i in range(100)])
df.set_index(s) # 指定一个索引
df.set_index([s, 'name']) # 同时指定索引和现有字段
df.set_index([s, s**2]) # 计算索引
以下为其他两个常用的操作:
df.set_index('month', drop=False) # 保留原列
df.set_index('month', append=True) # 保留原来的索引
有时我们想取消已有的索引,可以使用df.reset_index(),它的操作与set_index相反。以下是一些常用的操作:
df.reset_index() # 清除索引
df.set_index('month').reset_index() # 相当于什么也没做
# 删除原索引,month列没了
df.set_index('month').reset_index(drop=True)
df2.reset_index(inplace=True) # 覆盖使生效
# year一级索引取消
df.set_index(['month', 'year']).reset_index(level=1)
df2.reset_index(level='class') # 同上,使用层级索引名
df.reset_index(level='class', col_level=1) # 列索引
# 不存在层级名称的填入指定名称
df.reset_index(level='class', col_level=1, col_fill='species')
为了适应各种业务数据的处理,索引又针对各种类型数据定义了不同的索引类型。
数字索引(NumericIndex)共有以下几种。
RangeIndex:单调整数范围的不可变索引。
Int64Index:64位整型索引。
UInt64Index:无符号整数索引。
Float64Index:64位浮点型索引。
示例如下:
pd.RangeIndex(1,100,2)
# RangeIndex(start=1, stop=100, step=2)
pd.Int64Index([1,2,3,-4], name='num')
# Int64Index([1, 2, 3, -4], dtype='int64', name='num')
pd.UInt64Index([1,2,3,4])
# UInt64Index([1, 2, 3, 4], dtype='uint64')
pd.Float64Index([1.2,2.3,3,4])
# Float64Index([1.2, 2.3, 3.0, 4.0], dtype='float64')
类别索引(CategoricalIndex):类别只能包含有限数量的(通常是固定的)可能值(类别)。可以理解成枚举,比如性别只有男女,但在数据中每行都有,如果按文本处理会效率不高。类别的底层是pandas.Categorical。类别在第12章会专门讲解,只有在体量非常大的数据面前才能显示其优势。
pd.CategoricalIndex(['a', 'b', 'a', 'b'])
# CategoricalIndex(['a', 'b', 'a', 'b'], categories=['a', 'b'], ordered=False,
dtype='category')
间隔索引(IntervalIndex)代表每个数据的数值或者时间区间,一般应用于分箱数据。
pd.interval_range(start=0, end=5)
'''
IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
closed='right',
dtype='interval[int64]')
'''
多层索引(MultiIndex):多个层次且有归属关系的索引。
arrays = [[1, 1, 2, 2], ['red', 'blue', 'red', 'blue']]
pd.MultiIndex.from_arrays(arrays, names=('number', 'color'))
'''
MultiIndex([(1, 'red'),
(1, 'blue'),
(2, 'red'),
(2, 'blue')],
names=['number', 'color'])
'''
时间索引(DatetimeIndex):时序数据的时间。
# 从一个日期连续到另一个日期
pd.date_range(start='1/1/2018', end='1/08/2018')
# 指定开始时间和周期
pd.date_range(start='1/1/2018', periods=8)
# 以月为周期
pd.period_range(start='2017-01-01', end='2018-01-01', freq='M')
# 周期嵌套
pd.period_range(start=pd.Period('2017Q1', freq='Q'),
end=pd.Period('2017Q2', freq='Q'), freq='M')
时间差索引(TimedeltaIndex):代表时间长度的数据。
pd.TimedeltaIndex(data =['06:05:01.000030', '+23:59:59.999999',
'22 day 2 min 3us 10ns', '+23:29:59.999999',
'+12:19:59.999999'])
# 使用datetime
pd.TimedeltaIndex(['1 days', '1 days, 00:00:05',
np.timedelta64(2, 'D'),
datetime.timedelta(days=2, seconds=2)])
周期索引(PeriodIndex):一定频度的时间。
t = pd.period_range('2020-5-1 10:00:05', periods=8, freq='S')
pd.PeriodIndex(t,freq='S')
行和列的索引在Pandas里其实是一个Index对象,以下是创建一个Index对象的方法:
pd.Index([1, 2, 3])
# Int64Index([1, 2, 3], dtype='int64')
pd.Index(list('abc'))
# Index(['a', 'b', 'c'], dtype='object')
# 可以用name指定一个索引名称
pd.Index(['e', 'd', 'a', 'b'], name='something')
索引对象可以传入构建数据和读取数据的操作中。可以查看索引对象,列和行方向的索引对象如下:
df.index
# RangeIndex(start=0, stop=4, step=1)
df.columns
# Index(['month', 'year', 'sale'], dtype='object')
可以通过以下一系列操作查询索引的相关属性,以下方法也适用于df.columns,因为它们都是index对象。
# 常用属性
df.index.name # 名称
df.index.array # array数组
df.index.dtype # 数据类型
df.index.shape # 形状
df.index.size # 元素数量
df.index.values # array数组
# 其他,不常用
df.index.empty # 是否为空
df.index.is_unique # 是否不重复
df.index.names # 名称列表
df.index.is_all_dates # 是否全是日期时间
df.index.has_duplicates # 是否有重复值
df.index.values # 索引的值array
以下是索引的常用操作,这些操作会在我们今后处理数据中发挥作用。以下方法也适用于df.columns,因为都是index对象。
# 常用方法
df.index.astype('int64') # 转换类型
df.index.isin() # 是否存在,见下方示例
df.index.rename('number') # 修改索引名称
df.index.nunique() # 不重复值的数量
df.index.sort_values(ascending=False,) # 排序,倒序
df.index.map(lambda x:x+'_') # map函数处理
df.index.str.replace('_', '') # str替换
df.index.str.split('_') # 分隔
df.index.to_list() # 转为列表
df.index.to_frame(index=False, name='a') # 转成DataFrame
df.index.to_series() # 转为series
df.index.to_numpy() # 转为numpy
df.index.unique() # 去重
df.index.value_counts() # 去重及计数
df.index.where(df.index=='a') # 筛选
df.index.rename('grade', inplace=False) # 重命名索引
df.index.rename(['species', 'year']) # 多层,重命名索引
df.index.max() # 最大值
df.index.argmax() # 最大索引值
df.index.any()
df.index.all()
df.index.T # 转置,在多层索引里很有用
以下是一些不常用但很重要的操作:
# 其他,不常用
df.index.append(pd.Index([4,5])) # 追加
df.index.repeat(2) # 重复几次
df.index.inferred_type # 推测数据类型
df.index.hasnans # 有没有空值
df.index.is_monotonic_decreasing # 是否单调递减
df.index.is_monotonic # 是否有单调性
df.index.is_monotonic_increasing # 是否单调递增
df.index.nbytes # 基础数据中的字节数
df.index.ndim # 维度数,维数
df.index.nlevels # 索引层级数,通常为1
df.index.min() # 最小值
df.index.argmin() # 最小索引值
df.index.argsort() # 顺序值组成的数组
df.index.asof(2) # 返回最近的索引
# 索引类型转换
df.index.astype('int64', copy=True) # 深拷贝
# 拷贝
df.index.copy(name='new', deep=True, dtype='int64')
df.index.delete(1) # 删除指定位置
# 对比不同
df.index.difference(pd.Index([1,2,4]), sort=False)
df.index.drop('a', errors='ignore') # 删除
df.index.drop_duplicates(keep='first') # 去重值
df.index.droplevel(0) # 删除层级
df.index.dropna(how='all') # 删除空值
df.index.duplicated(keep='first') # 重复值在结果数组中为True
df.index.equals(df.index) # 与另一个索引对象是否相同
df.index.factorize() # 分解成(array:0-n, Index)
df.index.fillna(0, {0:'nan'}) # 填充空值
# 字符列表,把name值加在第一位,每个值加10
df.index.format(name=True, formatter=lambda x:x+10)
# 返回一个array,指定值的索引位数组,不在的为-1
df.index.get_indexer([2,9])
# 获取指定层级Index对象
df.index.get_level_values(0)
# 指定索引的位置,见示例
df.index.get_loc('b')
df.index.insert(2, 'f') # 在索引位2插入f
df.index.intersection(df.index) # 交集
df.index.is_(df.index) # 类似is检查
df.index.is_categorical() # 是否分类数据
df.index.is_type_compatible(df.index) # 类型是否兼容
df.index.is_type_compatible(1) # 类型是否兼容
df.index.isna() # array是否为空
df.index.isnull() # array是否缺失值
df.index.join(df.index, how='left') # 连接
df.index.notna() # 是否不存在的值
df.index.notnull() # 是否不存在的值
df.index.ravel() # 展平值的ndarray
df.index.reindex(['a','b']) # 新索引 (Index,array:0-n)
df.index.searchsorted('f') # 如果插入这个值,排序后在哪个索引位
df.index.searchsorted([0, 4]) # array([0, 3]) 多个
df.index.set_names('quarter') # 设置索引名称
df.index.set_names('species', level=0)
df.index.set_names(['kind', 'year'], inplace=True)
df.index.shift(10, freq='D') # 日期索引向前移动10天
idx1.symmetric_difference(idx2) # 两个索引不同的内容
idx1.union(idx2) # 拼接
df.add_prefix('t_') # 表头加前缀
df.add_suffix('_d') # 表头加后缀
df.first_valid_index() # 第一个有值的索引
df.last_valid_index() # 最后一个有值的索引
将一个数据列置为索引后,就不能再像修改列名那样修改索引的名称了,需要使用df.rename_axis方法。它不仅可以修改索引名,还可以修改列名。需要注意的是,这里修改的是索引名称,不是索引或者列名本身。
s.rename_axis("student_name") # 索引重命名
df.rename_axis(["dow", "hr"]) # 多层索引修改索引名
df.rename_axis('info', axis="columns") # 修改行索引名
# 修改多层列索引名
df.rename_axis(index={'a': 'A', 'b': 'B'})
# 修改多层列索引名
df.rename_axis(columns={'name': 's_name', 'b': 'B'})
df.rename_axis(columns=str.upper) # 行索引名变大写
用来修改行和列的索引名的主要函数是df.rename和df.set_axis。df.rename可以给定一个字典,键是原名称,值是想要修改的名称,还可以传入一个与原索引等长度序列进行覆盖修改,用一个函数处理原索引名。以下是一些具体的使用方法举例:
# 一一对应修改列索引
df.rename(columns={"A": "a", "B": "c"})
df.rename(str.lower, axis='columns')
# 修改行索引
df.rename(index={0: "x", 1: "y", 2: "z"})
df.rename({1: 2, 2: 4}, axis='index')
# 修改数据类型
df.rename(index=str)
# 重新修改索引
replacements = {l1:l2 for l1, l2 in zip(list1, list2)}
df.rename(replacements)
# 列名加前缀
df.rename(lambda x:'t_' + x, axis=1)
# 利用iter()函数的next特性修改
df.rename(lambda x, y=iter('abcdef'): next(y), axis=1)
# 修改列名,用解包形式生成新旧字段字典
df.rename(columns=dict(zip(df, list('abcd'))))
df.set_axis可以将所需的索引分配给给定的轴,通过分配类似列表或索引的方式来更改列标签或行标签的索引。
# 修改索引
df.set_axis(['a', 'b', 'c'], axis='index')
# 修改列名
df.set_axis(list('abcd'), axis=1)
# 使修改生效
df.set_axis(['a', 'b'], axis='columns', inplace=True)
# 传入索引内容
df.set_axis(pd.Index(list('abcde')), axis=0)
本节主要介绍DataFrame的基础信息和统计性信息。在我们拿到一个数据集,用Pandas载入后,需要做一些初步的验证,比如行名、列名是否一致,数据量是否有缺失,各列的数据类型等,让我们对数据的全貌有所了解。本节介绍的大多数功能对Series也是适用的。
df.head():前部数据,默认5条,可指定条数。
df.tail():尾部数据,默认5条,可指定条数。
df.sample():一条随机数据,可指定条数。
df = pd.read_excel('https://www.gairuo.com/file/data/dataset/team.xlsx')
s = df.Q1 # 取其中一列,形成Series
df.head() # 查看前5条数据
'''
name team Q1 Q2 Q3 Q4
0 Liver E 89 21 24 64
1 Arry C 36 37 37 57
2 Ack A 57 60 18 84
3 Eorge C 93 96 71 78
4 Oah D 65 49 61 86
'''
其他方法的使用如下:
df.head(10) # 查看前10条数据
s.tail() # 查看后5条数据
df.tail(10) # 查看后10条数据
df.sample() # 随机查看一条数据
s.sample(3) # 随机查看3条数据
执行df.shape会返回一个元组,该元组的第一个元素代表行数,第二个元素代表列数,这就是这个数据的基本形状,也是数据的大小。
df.shape
# (100, 6)
# 共100行6列(索引不算)
# Series 只有一个值
s.shape
# (100,)
执行df.info会显示所有数据的类型、索引情况、行列数、各字段数据类型、内存占用等。Series不支持。
df.info
'''
RangeIndex: 100 entries, 0 to 99
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 name 100 non-null object
1 team 100 non-null object
2 Q1 100 non-null int64
3 Q2 100 non-null int64
4 Q3 100 non-null int64
5 Q4 100 non-null int64
dtypes: int64(4), object(2)
memory usage: 4.8+ KB
'''
df.dtypes会返回每个字段的数据类型及DataFrame整体的类型。
df.dtypes
'''
name object
team object
Q1 int64
Q2 int64
Q3 int64
Q4 int64
dtype: object
'''
如果是Series,需要用s.dtype:
s.dtype
# dtype('int64')
df.axes会返回一个列内容和行内容组成的列表[列索引, 行索引]。
df.axes
'''
[RangeIndex(start=0, stop=100, step=1),
Index(['name', 'team', 'Q1', 'Q2', 'Q3', 'Q4'], dtype='object')]
'''
Series显示列索引,就是它的索引:
s.axes
# [RangeIndex(start=0, stop=100, step=1)]
除以上重要的几项信息外,以下信息也比较重要:
# 索引对象
df.index
# RangeIndex(start=0, stop=100, step=1)
# 列索引,Series不支持
df.columns
# Index(['name', 'team', 'Q1', 'Q2', 'Q3', 'Q4'], dtype='object')
df.values # array(<所有值的列表矩阵>)
df.ndim # 2 维度数
df.size # 600行×列的总数,就是总共有多少数据
# 是否为空,注意,有空值不认为是空
df.empty # False
# Series的索引,DataFrame的列名
df.keys()
此外,Series独有以下方法:
s.name # 'Q1'
s.array # 值组成的数组
s.dtype # 类型,dtype('int64')
s.hasnans # False
s.name可获取索引的名称,需要区分的是上例数据中df.name也能正常执行,它其实是df调用数据字段的方法,因为正好有名为name的列,如果没有就会报错,DataFrame是没有此属性的。
Pandas可以对Series与DataFrame进行快速的描述性统计,如求和、平均数、最大值、方差等,这些是最基础也最实用的统计方法。对于DataFrame,这些统计方法会按列进行计算,最终产出一个以列名为索引、以计算值为值的Series。
df.describe()会返回一个有多行的所有数字列的统计表,每一行对应一个统计指标,有总数、平均数、标准差、最小值、四分位数、最大值等。
df.describe()
'''
Q1 Q2 Q3 Q4
count 100.000000 100.000000 100.000000 100.000000
mean 49.200000 52.550000 52.670000 52.780000
std 29.962603 29.845181 26.543677 27.818524
min 1.000000 1.000000 1.000000 2.000000
25% 19.500000 26.750000 29.500000 29.500000
50% 51.500000 49.500000 55.000000 53.000000
75% 74.250000 77.750000 76.250000 75.250000
max 98.000000 99.000000 99.000000 99.000000
'''
如果没有数字,则会输出与字符相关的统计数据,如数量、不重复值数、最大值(字符按首字母顺序)等。示例如下。
pd.Series(['a', 'b', 'c', 'c']).describe()
'''
count 4
unique 3
top c
freq 2
dtype: object
'''
df.describe()也支持对时间数据的描述性统计:
(pd.Series(pd.date_range('2000-01-01', '2000-05-01'))
.describe(datetime_is_numeric=True)
)
'''
count 122
mean 2000-03-01 12:00:00
min 2000-01-01 00:00:00
25% 2000-01-31 06:00:00
50% 2000-03-01 12:00:00
75% 2000-03-31 18:00:00
max 2000-05-01 00:00:00
dtype: object
'''
还可以自己指定分位数(一般情况下,默认值包含中位数),指定和排除数据类型:
df.describe(percentiles=[.05, .25, .75, .95])
df.describe(include=[np.object, np.number]) # 指定类型
df.describe(exclude =[np.object]) # 排除类型
Pandas 支持常用的数学统计方法,如平均数、中位数、众数、方差等,还可以结合NumPy使用其更加丰富的统计功能。使用mean()计算平均数,DataFrame使用统计函数后会生成一个Series,这个Series的索引为每个数字类型列的列名,值为此列的平均数。如果DataFrame没有任何数字类型列,则会报错。
df.mean()
'''
Q1 49.20
Q2 52.55
Q3 52.67
Q4 52.78
dtype: float64
'''
type(df.mean())
# pandas.core.series.Series
Series应用数学统计函数一般会给出一个数字定值,直接计算出这一列的统计值:
df.Q1.mean()
s.mean()
# 49.2
如果我们希望按行计算平均数,即数据集中每个学生Q1到Q4的成绩的平均数,可以传入axis参数,列传index或0,行传columns或1:
df.mean(axis='columns')
df.mean(axis=1) # 效果同上
df.mean(1) # 效果同上
'''
0 49.50
1 41.75
2 54.75
3 84.50
4 65.25
...
95 67.00
96 31.25
97 53.00
98 58.50
99 44.75
Length: 100, dtype: float64
'''
它仅对数字类型的列起作用,会忽略文本等其他类型。我们发现,索引仍然是默认的自然索引,无法辨认是谁的成绩,所以可以先创建name为索引再进行计算:
# 创建name为索引,计算每行平均值,只看前5条
df.set_index('name').mean(1).head()
'''
name
Liver 49.50
Arry 41.75
Ack 54.75
Eorge 84.50
Oah 65.25
dtype: float64
'''
上文我们介绍了平均数mean,Pandas提供了非常多的数学统计方法,如下:
df.mean() # 返回所有列的均值
df.mean(1) # 返回所有行的均值,下同
df.corr() # 返回列与列之间的相关系数
df.count() # 返回每一列中的非空值的个数
df.max() # 返回每一列的最大值
df.min() # 返回每一列的最小值
df.abs() # 绝对值
df.median() # 返回每一列的中位数
df.std() # 返回每一列的标准差,贝塞尔校正的样本标准偏差
df.var() # 无偏方差
df.sem() # 平均值的标准误差
df.mode() # 众数
df.prod() # 连乘
df.mad() # 平均绝对偏差
df.cumprod() # 累积连乘,累乘
df.cumsum(axis=0) # 累积连加,累加
df.nunique() # 去重数量,不同值的量
df.idxmax() # 每列最大值的索引名
df.idxmin() # 每列最小值的索引名
df.cummax() # 累积最大值
df.cummin() # 累积最小值
df.skew() # 样本偏度(第三阶)
df.kurt() # 样本峰度(第四阶)
df.quantile() # 样本分位数(不同 % 的值)
Pandas还提供了一些特殊的用法:
# 很多支持指定行列(默认是axis=0列)等参数
df.mean(1) # 按行计算
# 很多函数均支持
df.sum(0, skipna=False) # 不除缺失数据
# 很多函数均支持
df.sum(level='blooded') # 索引级别
df.sum(level=0)
# 执行加法操作所需的最小有效值数
df.sum(min_count=1)
以上统计函数会有自己的一些特别的参数用于限制计算规则,可以在使用过程中利用Jupyter Notebook查看函数说明来了解。
除了简单的数学统计外,我们往往还需要对数据做非统计性计算,如去重、格式化等。接下来我们将介绍一些数据的加工处理方法。
df.all() # 返回所有列all()值的Series
df.any()
# 四舍五入
df.round(2) # 指定字段指定保留小数位,如有
df.round({'Q1': 2, 'Q2': 0})
df.round(-1) # 保留10位
# 每个列的去重值的数量
df.nunique()
s.nunique() # 本列的去重值
# 真假检测
df.isna() # 值的真假值替换
df.notna() # 与上相反
以下可以传一个值或者另一个DataFrame,对数据进行广播方式计算,返回计算后的DataFrame:
df + 1 # 等运算
df.add() # 加
df.sub() # 减
df.mul() # 乘
df.div() # 除
df.mod() # 模,除后的余数
df.pow() # 指数幂
df.dot(df2) # 矩阵运算
以下是Series专有的一些函数:
# 不重复的值及数量
s.value_counts()
s.value_counts(normalize=True) # 重复值的频率
s.value_counts(sort=False) # 不按频率排序
s.unique() # 去重的值 array
s.is_unique # 是否有重复
# 最大最小值
s.nlargest() # 最大的前5个
s.nlargest(15) # 最大的前15个
s.nsmallest() # 最小的前5个
s.nsmallest(15) # 最小的前15个
s.pct_change() # 计算与前一行的变化百分比
s.pct_change(periods=2) # 前两行
s1.cov(s2) # 两个序列的协方差
特别要掌握的是value_counts()和unique(),因为它们的使用频率非常高。
——————————————————————————————————