跳转至

Pandas

Pandas 包在 Numpy 数组的基础上添加了与 Excel 类似的行列标签。

对象的创建

  • 导入 Pandas 时,通常给其一个别名 "pd",即import pandas as pd
  • 作为标签库,Pandas 对象在 Numpy 数组基础上给予其行列标签,可以说,列表之于字典,就如 Numpy 之于 Pandas。
  • Pandas 中,所有数组特性仍在,Pandas 的数据以 Numpy 数组的方式存储。

一维对象的创建

字典创建法

NumPy中,可以通过 np.array()函数,将Python列表转化为NumPy数组;同样,Pandas中,可以通过pd.Series()函数,将Python字典转化为Series对象。

import pandas as pd
# 创建字典
dict_v = { 'a':0, 'b':0.25, 'c':0.5, 'd':0.75, 'e':1 }
# 用字典创建对象
sr = pd.Series( dict_v )
sr
a    0.00
b    0.25
c    0.50
d    0.75
e    1.00
dtype: float64

数组创建法

最直接的创建方法即直接给pd.Series()函数参数,其需要两个参数。第一个参数是值values(列表、数组、张量均可),第二个参数是键index(索引)。

# 先定义键与值
v = [0, 0.25, 0.5, 0.75, 1]
k = ['a', 'b', 'c', 'd', 'e']
# 用列表创建对象
sr = pd.Series( v, index=k )
sr
a    0.00
b    0.25
c    0.50
d    0.75
e    1.00
dtype: float64
sr.values
array([0.  , 0.25, 0.5 , 0.75, 1.  ])
# 其中,参数 index 可以省略,省略后索引即从 0 开始的顺序数字。
sr = pd.Series( v )
sr
0    0.00
1    0.25
2    0.50
3    0.75
4    1.00
dtype: float64

一维对象的属性

Series对象有两个属性:values与index。

import numpy as np
import pandas as pd
# 用数组创建 sr
v = np.array( [ 53, 64, 72, 82 ] )
k = ['1 号', '2 号', '3 号', '4 号']
sr = pd.Series( v, index=k )
sr
1 号    53
2 号    64
3 号    72
4 号    82
dtype: int64
# 查看 values 属性
sr.values
array([53, 64, 72, 82])
# 查看 index 属性
sr.index
Index(['1 号', '2 号', '3 号', '4 号'], dtype='object')

事实上,无论是用列表、数组还是张量来创建对象,最终valus均为数组

import pandas as pd
import torch
# 用张量创建 sr
v = torch.tensor( [ 53, 64, 72, 82 ] )
k = ['1 号', '2 号', '3 号', '4 号']
sr = pd.Series( v, index=k )
sr
1 号    53
2 号    64
3 号    72
4 号    82
dtype: int64
# 查看 values 的属性
sr.values
array([53, 64, 72, 82])

可见,虽然 Pandas 对象的第一个参数 values 可以传入列表、数组与张量,但传进去后默认的存储方式是 NumPy 数组。这一点更加提醒我们,Pandas 是建立在 NumPy 基础上的库,没有 NumPy 数组库就没有 Pandas 数据处理库。

当想要 Pandas 退化为 NumPy 时,查看其 values 属性即可。

二维对象的创建

二维对象将面向矩阵,其不仅有行标签 index,还有列标签 columns。

字典创建法

用字典创建二维对象时,必须基于多个Series对象,每一个Series就是一列数据,相当于对一列一列的数据进行拼接。

  • 创建Series对象时,字典的键是index,其延展方向是竖直的;
  • 创建DataFrame对象时,字典的键是columns,其延展方向是水平的。
import pandas as pd
# 创建 sr1:各个病人的年龄
v1 = [ 53, 64, 72, 82 ]
i = [ '1 号', '2 号', '3 号', '4 号' ]
sr1 = pd.Series( v1, index=i )
sr1
1 号    53
2 号    64
3 号    72
4 号    82
dtype: int64
# 创建 sr2:各个病人的性别
v2 = [ '女', '男', '男', '女' ]
i = [ '1 号', '2 号', '3 号', '4 号' ]
sr2 = pd.Series( v2, index=i )
sr2
1 号    女
2 号    男
3 号    男
4 号    女
dtype: object
# 创建 df 对象
df = pd.DataFrame( { '年龄':sr1, '性别':sr2 } )
df
年龄 性别
1 号 53
2 号 64
3 号 72
4 号 82

如果 sr1 和 sr2 的index不完全一致,那么二维对象的 index 会取 sr1 与 sr2 的所有 index,相应的,该对象就会产生一定数量的缺失值(NaN)。

import pandas as pd

# 创建 sr1:各个病人的年龄
v1 = [ 53, 64, 72, 82 ]
i = [ '1 号', '2 号', '3 号', '4 号' ]
sr1 = pd.Series( v1, index=i )

# 创建 sr2:各个病人的性别
v2 = [ '女', '男', '男', '女' ]
i = [ '1 号', '2 号', '3 号', '5 号' ]
sr2 = pd.Series( v2, index=i )

# 创建 df 对象
df = pd.DataFrame( { '年龄':sr1, '性别':sr2 } )
df
年龄 性别
1 号 53.0
2 号 64.0
3 号 72.0
4 号 82.0 NaN
5 号 NaN

数组创建法

最直接的创建方法即直接给pd.DataFrame函数参数,其需要三个参数。第一个参数是值values(数组),第二个参数是行标签index,第三个参数是列标签columns。其中,index和columns参数可以忽略,省略后即从0开始的顺序数字。

import numpy as np
import pandas as pd

# 设定键值
v = np.array( [ [53, '女'], [64, '男'], [72, '男'], [82, '女'] ] )
i = [ '1 号', '2 号', '3 号', '4 号' ]
c = [ '年龄', '性别' ]

# 数组创建法
df = pd.DataFrame( v, index=i, columns=c )
df
年龄 性别
1 号 53
2 号 64
3 号 72
4 号 82

细心的同学可能会发现端倪,NumPy 数组居然又含数字又含字符串,上次课中明明讲过数组只能容纳一种变量类型。这里的原理是,数组默默把数字转为了字符串,于是 v 就是一个字符串型数组。

二维对象的属性

DataFrame 对象有三个属性:values、index 与 columns。

import pandas as pd
# 设定键值
v = [ [53, '女'], [64, '男'], [72, '男'], [82, '女'] ]
i = [ '1 号', '2 号', '3 号', '4 号' ]
c = [ '年龄', '性别' ]
# 数组创建法
df = pd.DataFrame( v, index=i, columns=c )
df
年龄 性别
1 号 53
2 号 64
3 号 72
4 号 82
# 查看 values 属性
df.values
array([[53, '女'],
       [64, '男'],
       [72, '男'],
       [82, '女']], dtype=object)
# 查看 index 属性
df.index
Index(['1 号', '2 号', '3 号', '4 号'], dtype='object')
# 查看 columns 属性
df.columns
Index(['年龄', '性别'], dtype='object')

当想要 Pandas 退化为 NumPy 时,查看其 values 属性即可。

# 提取完整的数组
arr = df.values
print(arr)
[[53 '女']
 [64 '男']
 [72 '男']
 [82 '女']]
# 提取第[0]列,并转化为一个整数型数组
arr = arr[:,0].astype(int)
print(arr)
[53 64 72 82]

对象的索引

在学习 Pandas 的索引之前,需要知道

  • Pandas 的索引分为显式索引与隐式索引。显式索引是使用 Pandas 对象提供的索引,而隐式索引是使用数组本身自带的从 0 开始的索引。
  • 现假设某演示代码中的索引是整数,这个时候显式索引和隐式索引可能会出乱子。于是,Pandas 作者发明了索引器 loc(显式)与 iloc(隐式),手动告诉程序自己这句话是显式索引还是隐式索引。
  • 本章示例中,若代码块出现两栏,则左侧为显式索引,右侧为隐式索引,左右两列属于平行关系,任选其一均可。

一维对象的索引

访问元素

import pandas as pd

# 创建 sr
v = [ 53, 64, 72, 82 ]
k = ['1 号', '2 号', '3 号', '4 号']
sr = pd.Series( v, index=k )
sr
1 号    53
2 号    64
3 号    72
4 号    82
dtype: int64
# 通过显式索引访问元素
sr.loc[ '3 号' ]
np.int64(72)
# 通过隐式索引访问元素
sr.iloc[ 2 ]
np.int64(72)
# 通过显式索引进行花式索引
sr.loc[['1 号', '3 号']]
1 号    53
3 号    72
dtype: int64
# 通过隐式索引进行花式索引
sr.iloc[[0, 2]]
1 号    53
3 号    72
dtype: int64
# 通过显式索引修改元素
sr.loc['3 号'] = 100
sr
1 号     53
2 号     64
3 号    100
4 号     82
dtype: int64
# 通过隐式索引修改元素
sr.iloc[2] = 200
sr
1 号     53
2 号     64
3 号    200
4 号     82
dtype: int64

访问切片

使用显式索引时,'1 号':'3 号'可以覆盖最后一个'3 号',但隐式与之前一样。

import pandas as pd
# 创建 sr
v = [ 53, 64, 72, 82 ]
k = ['1 号', '2 号', '3 号', '4 号']
sr = pd.Series( v, index=k )
sr
1 号    53
2 号    64
3 号    72
4 号    82
dtype: int64
# 访问切片
sr.loc[ '1 号':'3 号' ]
1 号    53
2 号    64
3 号    72
dtype: int64
# 访问切片,可以看到还是左闭右开的
sr.iloc[ 0:3 ]
1 号    53
2 号    64
3 号    72
dtype: int64
# 切片仅是视图,改变切片实际上改变的是原数据
cut = sr.loc[ '1 号':'3 号' ]
cut.loc['1 号'] = 100
sr
1 号    100
2 号     64
3 号     72
4 号     82
dtype: int64
# 切片仅是视图
cut = sr.iloc[ 0:3 ]
cut.iloc[0] = 200
sr
1 号    200
2 号     64
3 号     72
4 号     82
dtype: int64
# 对象赋值仅是绑定,并非拷贝
cut = sr
cut.loc['3 号'] = 200
sr
1 号    200
2 号     64
3 号    200
4 号     82
dtype: int64
# 对象赋值仅是绑定
cut = sr
cut.iloc[2] = 300
sr
1 号    200
2 号     64
3 号    300
4 号     82
dtype: int64

若想创建新变量,与 NumPy 一样,使用.copy()方法即可。

如果去掉 .loc 和 .iloc ,此时与 NumPy 中的索引语法完全一致。

二维对象的索引

访问元素

import pandas as pd
# 字典创建法
i = [ '1 号', '2 号', '3 号', '4 号' ]
v1 = [ 53, 64, 72, 82 ]
v2 = [ '女', '男', '男', '女' ]
sr1 = pd.Series( v1, index=i )
sr2 = pd.Series( v2, index=i )
df = pd.DataFrame( { '年龄':sr1, '性别':sr2 } )
df
年龄 性别
1 号 53
2 号 64
3 号 72
4 号 82
# 访问元素
df.loc[ '1 号', '年龄' ]
np.int64(53)
# 访问元素
df.iloc[ 0, 0 ]
np.int64(53)
# 花式索引
df.loc[ ['1 号', '3 号'] , ['性别','年龄'] ]
性别 年龄
1 号 53
3 号 72
# 花式索引
df.iloc[ [0,2], [1,0] ]
性别 年龄
1 号 53
3 号 72
# 修改元素
df.loc[ '3 号', '年龄' ] = 100
df
年龄 性别
1 号 53
2 号 64
3 号 100
4 号 82
# 修改元素
df.iloc[ 2, 0 ] = 200
df
年龄 性别
1 号 53
2 号 64
3 号 200
4 号 82

在 NumPy 数组中,花式索引输出的是一个向量。但在 Pandas 对象中,考虑到其行列标签的信息不能丢失,所以输出一个向量就不行了,所以这里才输出一个二维对象。

访问切片

# 数组创建法
v = [ [53, '女'], [64, '男'], [72, '男'], [82, '女'] ]
i = [ '1 号', '2 号', '3 号', '4 号' ]
c = [ '年龄', '性别' ]
df = pd.DataFrame( v, index=i, columns=c )
df
年龄 性别
1 号 53
2 号 64
3 号 72
4 号 82
# 切片
df.loc[ '1 号':'3 号' , '年龄' ]
1 号    53
2 号    64
3 号    72
Name: 年龄, dtype: int64
# 切片
df.iloc[ 0:3 , 0 ]
1 号    53
2 号    64
3 号    72
Name: 年龄, dtype: int64
# 提取二维对象的行
df.loc[ '3 号' , : ]
年龄    72
性别     男
Name: 3 号, dtype: object
# 提取二维对象的行
df.iloc[ 2 , : ]
年龄    72
性别     男
Name: 3 号, dtype: object
# 提取矩阵对象的列
df.loc[ : , '年龄' ]
1 号    53
2 号    64
3 号    72
4 号    82
Name: 年龄, dtype: int64
# 提取矩阵对象的列
df.iloc[ : , 0 ]
1 号    53
2 号    64
3 号    72
4 号    82
Name: 年龄, dtype: int64

在显示索引中,提取矩阵的行或列还有一种简便写法,即

  • 提取二维对象的行:df.loc['3 号'] (原理是省略后面的冒号,隐式也可以)
  • 提取二维对象的列:df ['年龄'](原理是列标签本身就是二维对象的键)

对象的变形

对象的转置

有时候提供的大数据很畸形,行是特征,列是个体,这必须要先进行转置。

import pandas as pd
# 创建畸形 df
v = [ [53, 64, 72, 82], [ '女', '男', '男', '女' ] ]
i = [ '年龄', '性别' ]
c = [ '1 号', '2 号', '3 号', '4 号' ]
df = pd.DataFrame( v, index=i, columns=c )
df
1 号 2 号 3 号 4 号
年龄 53 64 72 82
性别
# 转置
df = df.T
df
年龄 性别
1 号 53
2 号 64
3 号 72
4 号 82

对象的翻转

紧接上面的例子,对Pandas对象进行左右翻转与上下翻转。

# 左右翻转
df = df.iloc[ : , : : -1 ]
df
性别 年龄
1 号 53
2 号 64
3 号 72
4 号 82
# 上下翻转
df = df.iloc[ : : -1 , : ]
df
性别 年龄
4 号 82
3 号 72
2 号 64
1 号 53

对象的重塑

考虑到对象是含有行列标签的,.reshape()已经不再适用,因此对象的重塑没有那么灵活。但可以做到将 sr 并入 df,也可以将 df 割出 sr。

# 数组法创建 sr
i = [ '1 号', '2 号', '3 号', '4 号' ]
v1 = [ 10, 20, 30, 40 ]
v2 = [ '女', '男', '男', '女' ]
v3 = [ 1, 2, 3, 4 ]
sr1 = pd.Series( v1, index=i )
sr2 = pd.Series( v2, index=i )
sr3 = pd.Series( v3, index=i )
sr1, sr2, sr3
(1 号    10
 2 号    20
 3 号    30
 4 号    40
 dtype: int64,
 1 号    女
 2 号    男
 3 号    男
 4 号    女
 dtype: object,
 1 号    1
 2 号    2
 3 号    3
 4 号    4
 dtype: int64)
# 字典法创建 df
df = pd.DataFrame( { '年龄':sr1, '性别':sr2 } )
df
年龄 性别
1 号 10
2 号 20
3 号 30
4 号 40
# 把 sr 并入 df 中
df['牌照'] = sr3
df
年龄 性别 牌照
1 号 10 1
2 号 20 2
3 号 30 3
4 号 40 4
# 把 df['年龄']分离成 sr4
sr4 = df['年龄']
sr4
1 号    10
2 号    20
3 号    30
4 号    40
Name: 年龄, dtype: int64

对象的拼接

Pandas中有一个pd.concat()函数,与np.concatenate()函数语法类似。

一维对象的合并

import pandas as pd
# 创建 sr1 和 sr2
v1 = [10, 20, 30, 40]
v2 = [40, 50, 60]
k1 = [ '1 号', '2 号', '3 号', '4 号' ]
k2 = [ '4 号', '5 号', '6 号' ]
sr1 = pd.Series( v1, index= k1 )
sr2 = pd.Series( v2, index= k2 )
sr1, sr2
(1 号    10
 2 号    20
 3 号    30
 4 号    40
 dtype: int64,
 4 号    40
 5 号    50
 6 号    60
 dtype: int64)
# 合并
pd.concat( [sr1, sr2] )
1 号    10
2 号    20
3 号    30
4 号    40
4 号    40
5 号    50
6 号    60
dtype: int64

值得注意的是,上面输出的键中出现了两个“4 号”,这是因为 Pandas 对象的属性,放弃了集合与字典索引中“不可重复”的特性,实际中,这可以拓展大数据分析与处理的应用场景。那么,如何保证索引是不重复的呢?对对象的属性 .index 或 .columns 使用 .is_unique 即可检查,返回 True表示行或列不重复,False 表示有重复。

一维对象与二维对象的合并

一维对象与二维对象的合并,即可理解为:给二维对象加上一列或者一行。因此,不必使用pd.concat()函数,只需要借助千问”二维对象的索引“语法。

# 首先,创建好二维对象

import pandas as pd

# 创建 sr1 与 sr2
v1 = [ 10, 20, 30]
v2 = [ '女', '男', '男']
sr1 = pd.Series( v1, index=[ '1 号', '2 号', '3 号'] )
sr2 = pd.Series( v2, index=[ '1 号', '2 号', '3 号'] )
sr1, sr2
(1 号    10
 2 号    20
 3 号    30
 dtype: int64,
 1 号    女
 2 号    男
 3 号    男
 dtype: object)
# 创建 df
df = pd.DataFrame( { '年龄':sr1, '性别':sr2 } )
df
年龄 性别
1 号 10
2 号 20
3 号 30
# 其次,为二维对象加上一列特征,可以是列表、数组、张量或一维对象。
# 加上一列
df['牌照'] = [1, 2, 3]
df
年龄 性别 牌照
1 号 10 1
2 号 20 2
3 号 30 3
# 最后,为二维对象加上一行个体,可以是列表、数组、张量或一维对象。
# 加上一行
df.loc['4 号'] = [40, '女', 4]
df
年龄 性别 牌照
1 号 10 1
2 号 20 2
3 号 30 3
4 号 40 4

二维对象的合并

二维对象合并仍然用 pd.concat()函数,不过其多了一个 axis 参数。

# 设定 df1、df2、df3
v1 = [ [10, '女'], [20, '男'], [30, '男'], [40, '女'] ]
v2 = [ [1, '是'], [2, '是'], [3, '是'], [4, '否'] ]
v3 = [ [50, '男', 5, '是'], [60, '女', 6, '是'] ]
i1 = [ '1 号', '2 号', '3 号', '4 号' ]
i2 = [ '1 号', '2 号', '3 号', '4 号' ]
i3 = [ '5 号', '6 号' ]
c1 = [ '年龄', '性别' ]
c2 = [ '牌照', 'ikun' ]
c3 = [ '年龄', '性别', '牌照', 'ikun' ]
df1 = pd.DataFrame( v1, index=i1, columns=c1 )
df2 = pd.DataFrame( v2, index=i2, columns=c2 )
df3 = pd.DataFrame( v3, index=i3, columns=c3 )
df1, df2, df3
(     年龄 性别
 1 号  10  女
 2 号  20  男
 3 号  30  男
 4 号  40  女,
      牌照 ikun
 1 号   1    是
 2 号   2    是
 3 号   3    是
 4 号   4    否,
      年龄 性别  牌照 ikun
 5 号  50  男   5    是
 6 号  60  女   6    是)
# 合并列对象(添加列特征)
df = pd.concat( [df1,df2], axis=1 )
df
年龄 性别 牌照 ikun
1 号 10 1
2 号 20 2
3 号 30 3
4 号 40 4
# 合并行对象(添加行个体)
df = pd.concat( [df,df3] )
df
年龄 性别 牌照 ikun
1 号 10 1
2 号 20 2
3 号 30 3
4 号 40 4
5 号 50 5
6 号 60 6

对象的运算

对象与系数之间的运算

下列演示代码中,左侧为一维对象,右侧为二维对象。

import pandas as pd
# 创建 sr
sr = pd.Series( [ 53, 64, 72 ] , index=['1 号', '2 号', '3 号'] )
sr
1 号    53
2 号    64
3 号    72
dtype: int64
# 创建 df
v = [ [53, '女'], [64, '男'], [72, '男'] ]
df = pd.DataFrame( v, index=[ '1 号', '2 号', '3 号' ], columns=[ '年龄', '性别' ] )
df
年龄 性别
1 号 53
2 号 64
3 号 72
# 对一维对象进行操作
sr = sr + 10
sr
1 号    63
2 号    74
3 号    82
dtype: int64
# 对二维对象进行操作
df['年龄'] = df['年龄'] + 10
df
年龄 性别
1 号 63
2 号 74
3 号 82
sr = sr * 10
sr
1 号    630
2 号    740
3 号    820
dtype: int64
df['年龄'] = df['年龄'] * 10
df
年龄 性别
1 号 630
2 号 740
3 号 820
sr = sr ** 2
sr
1 号    396900
2 号    547600
3 号    672400
dtype: int64
df['年龄'] = df['年龄'] ** 2
df
年龄 性别
1 号 396900
2 号 547600
3 号 672400

对象与对象之间的运算

对象做运算,必须保证其都是数字型对象,两个对象之间的维度可以不同。

一维对象之间的运算

import pandas as pd
# 创建 sr1
v1 = [10, 20, 30, 40]
k1 = [ '1 号', '2 号', '3 号', '4 号' ]
sr1 = pd.Series( v1, index= k1 )
sr1
1 号    10
2 号    20
3 号    30
4 号    40
dtype: int64
# 创建 sr2
v2 = [1, 2, 3 ]
k2 = [ '1 号', '2 号', '3 号' ]
sr2 = pd.Series( v2, index= k2 )
sr2
1 号    1
2 号    2
3 号    3
dtype: int64
sr1 + sr2 # 加法
1 号    11.0
2 号    22.0
3 号    33.0
4 号     NaN
dtype: float64
sr1 - sr2 # 减法
1 号     9.0
2 号    18.0
3 号    27.0
4 号     NaN
dtype: float64
sr1 * sr2 # 乘法
1 号    10.0
2 号    40.0
3 号    90.0
4 号     NaN
dtype: float64
sr1 / sr2 # 除法
1 号    10.0
2 号    10.0
3 号    10.0
4 号     NaN
dtype: float64
sr1 ** sr2 # 幂方
1 号       10.0
2 号      400.0
3 号    27000.0
4 号        NaN
dtype: float64

二维对象之间的运算

import pandas as pd
# 设定 df1 和 df2
v1 = [ [10, '女'], [20, '男'], [30, '男'], [40, '女'] ]
v2 = [ 1, 2 ,3, 6 ]
i1 = [ '1 号', '2 号', '3 号', '4 号' ]; c1 = [ '年龄', '性别' ]
i2 = [ '1 号', '2 号', '3 号', '6 号' ]; c2 = [ '牌照' ]
df1 = pd.DataFrame( v1, index=i1, columns=c1 )
df2 = pd.DataFrame( v2, index=i2, columns=c2 )
df1, df2
(     年龄 性别
 1 号  10  女
 2 号  20  男
 3 号  30  男
 4 号  40  女,
      牌照
 1 号   1
 2 号   2
 3 号   3
 6 号   6)
# 加法
df1['加法'] = df1['年龄'] + df2['牌照']
df1
年龄 性别 加法
1 号 10 11.0
2 号 20 22.0
3 号 30 33.0
4 号 40 NaN
# 减法、乘法、除法、幂方
df1['减法'] = df1['年龄'] - df2['牌照']
df1['乘法'] = df1['年龄'] * df2['牌照']
df1['除法'] = df1['年龄'] / df2['牌照']
df1['幂方'] = df1['年龄'] ** df2['牌照']
df1
年龄 性别 加法 减法 乘法 除法 幂方
1 号 10 11.0 9.0 10.0 10.0 10.0
2 号 20 22.0 18.0 40.0 10.0 400.0
3 号 30 33.0 27.0 90.0 10.0 27000.0
4 号 40 NaN NaN NaN NaN NaN

本章的最后,补充两点内容,读者可自行尝试。

  • 使用 np.abs()、np.cos()、np.exp()、np.log() 等数学函数时,会保留索引;
  • Pandas 中仍然存在布尔型对象,用法与 NumPy 无异,会保留索引。
import pandas as pd
import numpy as np

# 创建一个带有自定义索引的 Series
data = pd.Series([-1.5, 0, 2.5, -3.0], index=['a', 'b', 'c', 'd'])

print("原始数据 (Original Data):")
print(data)
print("-" * 30)

# 验证 np.abs() - 绝对值
print("使用 np.abs() 后 (索引保留):")
print(np.abs(data))
print("-" * 30)

# 验证 np.exp() - 指数
print("使用 np.exp() 后 (索引保留):")
print(np.exp(data))
print("-" * 30)

# 验证 np.log() - 对数 (注意:log(0) 和 log(负数) 会产生 inf 或 nan,但不影响索引保留)
print("使用 np.log() 后 (索引保留):")
# 为了演示 log,我们换一组正数数据
data_pos = pd.Series([1, 2.718, 10], index=['x', 'y', 'z'])
print(np.log(data_pos))
原始数据 (Original Data):
a   -1.5
b    0.0
c    2.5
d   -3.0
dtype: float64
------------------------------
使用 np.abs() 后 (索引保留):
a    1.5
b    0.0
c    2.5
d    3.0
dtype: float64
------------------------------
使用 np.exp() 后 (索引保留):
a     0.223130
b     1.000000
c    12.182494
d     0.049787
dtype: float64
------------------------------
使用 np.log() 后 (索引保留):
x    0.000000
y    0.999896
z    2.302585
dtype: float64
import pandas as pd
import numpy as np

# 创建一个 Series
s = pd.Series([10, -5, 0, 20, -15], index=['A', 'B', 'C', 'D', 'E'])

print("原始数据:")
print(s)
print("-" * 30)

# 1. 生成布尔对象 (Boolean Object)
# 用法与 NumPy 无异:直接比较
bool_mask = s > 0
print("布尔对象 (s > 0):")
print(bool_mask)
# 注意看:结果是一个 Series,且索引 ['A', 'B', ...] 被完整保留了
print("-" * 30)

# 2. 布尔运算 (Logical Operations)
# 用法与 NumPy 无异:使用 & (与), | (或), ~ (非)
# 比如:找出 大于0 且 小于15 的数
complex_mask = (s > 0) & (s < 15)
print("复杂布尔运算 ((s > 0) & (s < 15)):")
print(complex_mask)
print("-" * 30)

# 3. 布尔索引 (Boolean Indexing)
# 使用上面的布尔对象来筛选数据
filtered_data = s[bool_mask]
print("筛选后的数据 (s[s > 0]):")
print(filtered_data)
# 注意看:筛选出来的结果,其索引依然是原始的 'A' 和 'D',而不是重置为 0, 1
原始数据:
A    10
B    -5
C     0
D    20
E   -15
dtype: int64
------------------------------
布尔对象 (s > 0):
A     True
B    False
C    False
D     True
E    False
dtype: bool
------------------------------
复杂布尔运算 ((s > 0) & (s < 15)):
A     True
B    False
C    False
D    False
E    False
dtype: bool
------------------------------
筛选后的数据 (s[s > 0]):
A    10
D    20
dtype: int64

对象的缺失值

发现缺失值

发现缺失值使用.isnull()方法。

import pandas as pd
# 创建 sr
v = [ 53, None, 72, 82 ]
k = ['1 号', '2 号', '3 号', '4 号']
sr = pd.Series( v, index=k )
sr
1 号    53.0
2 号     NaN
3 号    72.0
4 号    82.0
dtype: float64
# 创建 df
v = [ [None, 1], [64, None], [72, 3], [82, 4] ]
i = [ '1 号', '2 号', '3 号', '4 号' ]
c = [ '年龄', '牌照' ]
df = pd.DataFrame( v, index=i, columns=c )
df
年龄 牌照
1 号 NaN 1.0
2 号 64.0 NaN
3 号 72.0 3.0
4 号 82.0 4.0
# 发现 sr 的缺失值
sr.isnull()
1 号    False
2 号     True
3 号    False
4 号    False
dtype: bool
# 发现 df 的缺失值
df.isnull()
年龄 牌照
1 号 True False
2 号 False True
3 号 False False
4 号 False False
# 发现 df 的非缺失值
~df.isnull()
年龄 牌照
1 号 False True
2 号 True False
3 号 True True
4 号 True True
df.notnull()
年龄 牌照
1 号 False True
2 号 True False
3 号 True True
4 号 True True

除了.isnull() 方法,还有一个与之相反的 .notnull() 方法,但不如在开头加一个非号“~”即可。

剔除缺失值

剔除缺失值使用 .dropna() 方法,一维对象很好剔除;二维对象比较复杂,要么单独剔除 df 中含有缺失值的行,要么剔除 df 中含有缺失值的列。

import pandas as pd
# 创建 sr
v = [ 53, None, 72, 82 ]
k = ['1 号', '2 号', '3 号', '4 号']
sr = pd.Series( v, index=k )
sr
1 号    53.0
2 号     NaN
3 号    72.0
4 号    82.0
dtype: float64
# 创建 df
v = [ [None, None], [64, None], [72, 3], [82, 4] ]
i = [ '1 号', '2 号', '3 号', '4 号' ]
c = [ '年龄', '牌照' ]
df = pd.DataFrame( v, index=i, columns=c )
df
年龄 牌照
1 号 NaN NaN
2 号 64.0 NaN
3 号 72.0 3.0
4 号 82.0 4.0
# 剔除 sr 的缺失值
sr.dropna()
1 号    53.0
3 号    72.0
4 号    82.0
dtype: float64
# 剔除 df 含缺失值的行
df.dropna()
年龄 牌照
3 号 72.0 3.0
4 号 82.0 4.0

把含有 NaN 的行剔除掉了,你也可以通过 df.dropna(axis='columns')的方式剔除列。但请警惕,一般都是剔除行,只因大数据中行是个体,列是特征。

有些同学认为,只要某行含有一个 NaN 就剔除该个体太过残忍,我们可以设定一个参数,只有当该行全部是 NaN,才剔除该列特征。

# 剔除 df 全是 NaN 的个体
df.dropna(how='all')
年龄 牌照
2 号 64.0 NaN
3 号 72.0 3.0
4 号 82.0 4.0

填补缺失值

填充缺失值使用 .fillna() 方法,实际的数据填充没有统一的方法,很灵活。

一维对象

import pandas as pd
# 创建 sr
v = [ 53, None, 72, 82 ]
sr = pd.Series( v, index=['1 号', '2 号', '3 号', '4 号'] )
sr
1 号    53.0
2 号     NaN
3 号    72.0
4 号    82.0
dtype: float64
# 用常数(0)填充
sr.fillna(0)
1 号    53.0
2 号     0.0
3 号    72.0
4 号    82.0
dtype: float64
# 用常数(均值)填充
import numpy as np
sr.fillna(np.mean(sr))
1 号    53.0
2 号    69.0
3 号    72.0
4 号    82.0
dtype: float64
# 用前值填充
sr.fillna(method='ffill')
/tmp/ipykernel_55/89089568.py:2: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.
  sr.fillna(method='ffill')





1 号    53.0
2 号    53.0
3 号    72.0
4 号    82.0
dtype: float64
# 用后值填充
sr.fillna(method='bfill')
/tmp/ipykernel_55/568087098.py:2: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.
  sr.fillna(method='bfill')





1 号    53.0
2 号    72.0
3 号    72.0
4 号    82.0
dtype: float64

二维对象

import pandas as pd
# 设定 df
v = [ [None, None], [64, None], [72, 3], [82, 4] ]
i = [ '1 号', '2 号', '3 号', '4 号' ]; c = [ '年龄', '牌照' ]
df = pd.DataFrame( v, index=i, columns=c )
df
年龄 牌照
1 号 NaN NaN
2 号 64.0 NaN
3 号 72.0 3.0
4 号 82.0 4.0
# 用常数(0)填充
df.fillna(0)
年龄 牌照
1 号 0.0 0.0
2 号 64.0 0.0
3 号 72.0 3.0
4 号 82.0 4.0
# 用常数(均值)填充
import numpy as np
df.fillna( np.mean(df) )
年龄 牌照
1 号 45.0 45.0
2 号 64.0 45.0
3 号 72.0 3.0
4 号 82.0 4.0

似乎此处的结果并不像教程中所示,np.mean(df)计算了所有数值的平均值并以此填充了所有缺失字段,而没有按照每一列计算均值并填充。

使用df.mean()反而能实现我们预期的效果。

# 用常数(均值)填充
df.fillna( df.mean() )
年龄 牌照
1 号 72.666667 3.5
2 号 64.000000 3.5
3 号 72.000000 3.0
4 号 82.000000 4.0
# 用前值填充
df.ffill()
年龄 牌照
1 号 NaN NaN
2 号 64.0 NaN
3 号 72.0 3.0
4 号 82.0 4.0
# 用后值填充
df.bfill()
年龄 牌照
1 号 64.0 3.0
2 号 64.0 3.0
3 号 72.0 3.0
4 号 82.0 4.0

导入Excel文件

创建Excel文件

导入信息的命令如示例所示。

import pandas as pd
import pandas as pd

# 读取 Excel 文件
df_1 = pd.read_excel('/kaggle/input/datasets/frechen026/jie-pandas/Data.xlsx')
df_2 = pd.read_excel('/kaggle/input/datasets/frechen026/jie-pandas/Data_1773733480020.xlsx')
df_3 = pd.read_excel('/kaggle/input/datasets/frechen026/jie-pandas/test1.xlsx')
df_4 = pd.read_excel('/kaggle/input/datasets/frechen026/jie-pandas/test2.xlsx')


# 保存为 CSV 文件
df_1.to_csv('/kaggle/working/Data1.csv', index=False, encoding='utf-8-sig')
df_2.to_csv('/kaggle/working/Data2.csv', index=False, encoding='utf-8-sig')
df_3.to_csv('/kaggle/working/test1.csv', index=False, encoding='utf-8-sig')
df_4.to_csv('/kaggle/working/test2.csv', index=False, encoding='utf-8-sig')
# 导入 Pandas 对象
df = pd.read_csv('/kaggle/working/Data1.csv', index_col=0)
df
age gender num kun
Unnamed: 0
1号 10 1
2号 20 2
3号 30 3
4号 40 4
5号 50 5
6号 60 6
# 提取纯数组
arr = df.values
arr
array([[10, '女', 1, '是'],
       [20, '男', 2, '是'],
       [30, '男', 3, '是'],
       [40, '女', 4, '否'],
       [50, '男', 5, '是'],
       [60, '女', 6, '是']], dtype=object)

数据分析

导入信息

首先,准备好Excel数据表格。

发现没有行标签,因此需要在最左侧快速填充一列顺序数字。

接着,按照第六章的方法,将其另存为 CSV 文件,并放置项目文件夹中。

最后,导入该表格至 Jupyter 中。

import pandas as pd
# 导入 Pandas 对象
pd.read_csv('/kaggle/working/test2.csv', index_col=0)
发现时间 发现数量 观测方法 行星质量 距地距离 轨道周期
Unnamed: 0
0 1989 1 径向速度 11.680 40.57 83.888000
1 1992 3 脉冲星计时 NaN NaN 25.262000
2 1992 3 脉冲星计时 NaN NaN 66.541900
3 1994 3 脉冲星计时 NaN NaN 98.211400
4 1995 1 径向速度 0.472 15.36 4.230785
... ... ... ... ... ... ...
1030 2014 1 凌日 NaN NaN 2.465020
1031 2014 1 凌日 NaN NaN 68.958400
1032 2014 1 凌日 NaN 1056.00 1.720861
1033 2014 1 凌日 NaN NaN 66.262000
1034 2014 1 凌日 NaN 470.00 0.925542

1035 rows × 6 columns

聚合方法

可在输出df时,对其使用.head()方法,使其仅仅输出前5行。

import pandas as pd
# 导入 Pandas 对象
df = pd.read_csv('/kaggle/working/test2.csv', index_col=0)
df.head()
发现时间 发现数量 观测方法 行星质量 距地距离 轨道周期
Unnamed: 0
0 1989 1 径向速度 11.680 40.57 83.888000
1 1992 3 脉冲星计时 NaN NaN 25.262000
2 1992 3 脉冲星计时 NaN NaN 66.541900
3 1994 3 脉冲星计时 NaN NaN 98.211400
4 1995 1 径向速度 0.472 15.36 4.230785

NumPy 中所有的聚合函数对 Pandas 对象均适用。此外,Pandas 将这些函数变为对象的方法,这样,不导入 NumPy 也可使用。

# 最大值函数 np.max( )
df.max()
发现时间        2014
发现数量           7
观测方法      轨道亮度调制
行星质量        25.0
距地距离      8500.0
轨道周期    730000.0
dtype: object
# 最小值函数 np.min( )
df.min()
发现时间        1989
发现数量           1
观测方法          凌日
行星质量      0.0036
距地距离        1.35
轨道周期    0.090706
dtype: object
# 均值函数 np.mean( )
df.mean(numeric_only=True)
# 在 mean() 函数中添加参数 numeric_only=True。这会告诉 Pandas 自动忽略所有非数值类型的列,只对数字列计算均值。

# 可能是版本原因我这里需要添加该参数。
发现时间    2009.070531
发现数量       1.785507
行星质量       2.638161
距地距离     264.069282
轨道周期    2002.917596
dtype: float64
# 求和函数 np.sum( )
df.sum(numeric_only=True)
发现时间    2.079388e+06
发现数量    1.848000e+03
行星质量    1.353376e+03
距地距离    2.133680e+05
轨道周期    1.986894e+06
dtype: float64

描述方法

在数据分析中,用以上方法挨个查看未免太过麻烦,可以使用 .describe() 方法直接查看所有聚合函数的信息。

# 导入 Pandas 对象
df = pd.read_csv('/kaggle/working/test2.csv', index_col=0)
df.head()
发现时间 发现数量 观测方法 行星质量 距地距离 轨道周期
Unnamed: 0
0 1989 1 径向速度 11.680 40.57 83.888000
1 1992 3 脉冲星计时 NaN NaN 25.262000
2 1992 3 脉冲星计时 NaN NaN 66.541900
3 1994 3 脉冲星计时 NaN NaN 98.211400
4 1995 1 径向速度 0.472 15.36 4.230785
df.describe()
发现时间 发现数量 行星质量 距地距离 轨道周期
count 1035.000000 1035.000000 513.000000 808.000000 992.000000
mean 2009.070531 1.785507 2.638161 264.069282 2002.917596
std 3.972567 1.240976 3.818617 733.116493 26014.728304
min 1989.000000 1.000000 0.003600 1.350000 0.090706
25% 2007.000000 1.000000 0.229000 32.560000 5.442540
50% 2010.000000 1.000000 1.260000 55.250000 39.979500
75% 2012.000000 2.000000 3.040000 178.500000 526.005000
max 2014.000000 7.000000 25.000000 8500.000000 730000.000000

数据透视

两个特征内的数据透视

数据透视,对数据分析来讲十分重要。

现以泰坦尼克号的生还数据为例,以“是否生还”特征为考察的核心(或者说是神经网络的输出),研究其它特征(输入)与之的关系,如示例所示。

import pandas as pd
# 导入 Pandas 对象
df = pd.read_csv('/kaggle/working/test1.csv', index_col=0)
df.head()
性别 年龄 船舱等级 费用 是否生还
Unnamed: 0
0 22.0 三等 7.2500 0
1 38.0 一等 71.2833 1
2 26.0 三等 7.9250 1
3 35.0 一等 53.1000 1
4 35.0 三等 8.0500 0
# 一个特征:性别
df.pivot_table('是否生还', index='性别')
是否生还
性别
0.742038
0.188908
# 两个特征:性别、船舱等级
df.pivot_table('是否生还', index='性别', columns='船舱等级')
船舱等级 一等 三等 二等
性别
0.968085 0.500000 0.921053
0.368852 0.135447 0.157407

在上述示例中,数据透视表中的数值默认是输出特征“是否生还”的均值(mean),行标签和列标签变成了其它的输入特征。可以看出,女性整体的生还概率是 74%,男性整体的生还概率为 18%。

按照船舱等级的下降,生还率逐步下降。 值得注意的是,pivot_table() 方法有一个很重要的参数:aggfunc,其默认值是'mean',除此以外,所有的聚合函数'max'、'min'、'sum'、'count' 均可使用。

显然,对于这里的“是否生还”来说,'mean'就是最好的选择,其刚好为概率。

多个特征的数据透视

前面的示例只涉及到两个特征,有时需要考察更多特征与输出特征的关系。

这里,将年龄和费用都加进去。但是,这两个特征的数值很分散,之前的性别和船舱等级都可以按照类别分,现在已经不能再按类别分了。因此,需要涉及****到数据透视表配套的两个重要函数:pd.cut()与 pd.qcut()。

# 导入 Pandas 对象
df = pd.read_csv('/kaggle/working/test1.csv', index_col=0)
df.head()
性别 年龄 船舱等级 费用 是否生还
Unnamed: 0
0 22.0 三等 7.2500 0
1 38.0 一等 71.2833 1
2 26.0 三等 7.9250 1
3 35.0 一等 53.1000 1
4 35.0 三等 8.0500 0
# 三个特征:性别、船舱等级、年龄
age = pd.cut( df['年龄'], [0,18,120] ) # 以 18 岁为分水岭
df.pivot_table('是否生还', index= ['性别', age], columns='船舱等级')
/tmp/ipykernel_55/1361481379.py:3: FutureWarning: The default value of observed=False is deprecated and will change to observed=True in a future version of pandas. Specify observed=False to silence this warning and retain the current behavior
  df.pivot_table('是否生还', index= ['性别', age], columns='船舱等级')
船舱等级 一等 三等 二等
性别 年龄
(0, 18] 0.909091 0.511628 1.000000
(18, 120] 0.972973 0.423729 0.900000
(0, 18] 0.800000 0.215686 0.600000
(18, 120] 0.375000 0.133663 0.071429
# 四个特征:性别、船舱等级、年龄、费用
fare = pd.qcut( df['费用'], 2 ) # 将费用自动分为两部分
df.pivot_table('是否生还', index= ['船舱等级',fare], columns=['性别', age])
/tmp/ipykernel_55/2785335320.py:3: FutureWarning: The default value of observed=False is deprecated and will change to observed=True in a future version of pandas. Specify observed=False to silence this warning and retain the current behavior
  df.pivot_table('是否生还', index= ['船舱等级',fare], columns=['性别', age])
性别
年龄 (0, 18] (18, 120] (0, 18] (18, 120]
船舱等级 费用
一等 (-0.001, 14.454] NaN NaN NaN 0.000000
(14.454, 512.329] 0.909091 0.972973 0.800000 0.391304
三等 (-0.001, 14.454] 0.714286 0.444444 0.260870 0.125000
(14.454, 512.329] 0.318182 0.391304 0.178571 0.192308
二等 (-0.001, 14.454] 1.000000 0.880000 0.000000 0.098039
(14.454, 512.329] 1.000000 0.914286 0.818182 0.030303
  • pd.cut()函数需要手动设置分割点,也可以设置为[0,18,60,120]
  • pd.qcut()函数可自动分割,如果需要分割成 3 部分,可以设置为 3。