pandas(3)常用函数操作

pandas 第三部分:常用函数操作

Posted by Hilda on February 24, 2025

NumPy 博客总结:

《Python数据分析基础教程:NumPy学习指南(第2版)》所有章节阅读笔记+代码

70道NumPy 面试题(题目+答案)

pandas博客总结:

pandas(1)数据预处理

pandas(2)数据分析


数据分析处理库Pandas(3)

3 常用函数操作

在数据处理过程中经常要对数据做各种变换,Pandas提供了非常丰富的函数来帮大家完成每一项功能,不仅如此,如果要实现的功能过于复杂,也可以间接使用自定义函数。

3.1 Merge操作

数据处理中可能经常要对提取的特征进行整合,例如后续实战中会拿到一份歌曲数据集,但是不同的文件存储的特征不同,有的文件包括歌曲名、播放量;有的包括歌曲名、歌手名。现在我们要做的就是把所有特征汇总在一起,例如以歌曲为索引来整合。

为了演示Merge函数的操作,先创建两个DataFrame:

1
2
3
4
5
6
7
8
9
left = pd.DataFrame({'key':['K0','K1','K2','K3'],
                    'A':['A0','A1','A2','A3'],
                    'B':['B0','B1','B2','B3']})

right = pd.DataFrame({'key':['K0','K1','K2','K3'],
                    'C':['C0','C1','C2','C3'],
                    'D':['D0','D1','D2','D3']})
display(left)
display(right)

image-20250224155819218

1
2
res = pd.merge(left, right, on="key")
res

image-20250224161112118

现在按照key列把两份数据整合在一起了,key列在left和right两份数据中恰好都一样,试想:如果不相同,结果会发生变化吗?

1
2
3
4
5
6
7
8
9
10
11
left = pd.DataFrame({'key1':['K0','K1','K2','K3'],
                     'key2':['K0','K1','K2','K3'],
                    'A':['A0','A1','A2','A3'],
                    'B':['B0','B1','B2','B3']})

right = pd.DataFrame({'key1':['K0','K1','K2','K3'],
                      'key2':['K0','K1','K2','K4'],
                    'C':['C0','C1','C2','C3'],
                    'D':['D0','D1','D2','D3']})
display(left)
display(right)

image-20250224161227563

细心的读者应该发现,两份数据key1列和key2列的前3行都相同,但是第4行的值不同,这会对结果产生什么影响吗?

1
2
res = pd.merge(left, right, on=['key1', 'key2'])
res

image-20250224161329485

输出结果显示前3行相同的都组合在一起了,但是第4行却被直接抛弃了。如果想考虑所有的结果,还需要额外设置一个how参数:

1
2
res = pd.merge(left, right, on=['key1', 'key2'], how='outer')
res

image-20250224161402190

还可以加入详细的组合说明,指定indicator参数为True即可:

1
2
res = pd.merge(left, right, on=['key1', 'key2'], how='outer', indicator=True)
res

image-20250224161443930

也可以单独设置只考虑左边数据或者只考虑右边数据,说白了就是以谁为准:

1
2
res = pd.merge(left, right, on=['key1', 'key2'], how='left')
res

image-20250224161602101

1
2
res = pd.merge(left, right, on=['key1', 'key2'], how='right')
res

image-20250224161617732

在数据特征组合时经常要整合大量数据源,熟练使用Merge函数可以帮助大家快速处理数据。

3.2 排序操作

排序操作的用法也是十分简洁,先来创建一个DataFrame:

1
2
data = pd.DataFrame({"group":['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'], 'data': [4, 3, 2, 1, 12, 3, 4, 5, 7]})
data

image-20250224191920474

排序的时候,可以指定升序或者降序,并且还可以指定按照多个指标排序:

1
2
data.sort_values(by=["group", 'data'], ascending=[False, True], inplace=True)
data

image-20250224194515588

上述操作表示首先对group列按照降序进行排列,在此基础上保持data列是升序排列,其中by参数用于设置要排序的列,ascending参数用于设置升降序。

3.3 缺失值处理

拿到一份数据之后,经常会遇到数据不干净的现象,即里面可能存在缺失值或者重复片段,这就需要先进行预处理操作。再来创建一组数据,如果有重复部分,也可以直接用乘法来创建一组数据:

1
2
3
data = pd.DataFrame({'k1':['one']*3+['two']*4,
                   'k2':[3,2,1,3,3,4,4]})
data

image-20250224194729856

此时数据中有几条完全相同的,可以使用drop_duplicates()函数去掉多余的数据:

1
data.drop_duplicates()

image-20250224194809484

也可以只考虑某一列的重复情况,其他全部舍弃:

1
data.drop_duplicates(subset="k1")

image-20250224194903244

如果要往数据中添加新的列呢?可以直接指定新的列名或者使用assign()函数:

1
2
3
4
data = pd.DataFrame({"data1": np.random.randn(5), "data2": np.random.randn(5)})
display(data)
data = data.assign(ration = data['data1']/data['data2'])
display(data)

image-20250224195145501

数据处理过程中经常会遇到缺失值,Pandas中一般用NaN来表示(Not a Number),拿到数据之后,通常都会先看一看缺失情况:

1
2
3
import numpy as np
df = pd.DataFrame([range(3), [0, np.nan, 0], [0, 0, np.nan], range(3)])
df

image-20250224195342079

在创建的时候加入两个缺失值,可以直接通过isnull()函数判断所有缺失情况:

1
df.isnull()

image-20250224195350840

输出结果显示了全部数据缺失情况,其中True代表数据缺失。如果数据量较大,总不能一行一行来核对,更多的时候,我们想知道某列是否存在缺失值:

1
df.isnull().any()

image-20250224195427335

其中.any()函数相当于只要有一个缺失值就意味着存在缺失情况,当然也可以自己指定检查的维度:

1
df.isnull().any(axis = 1)

image-20250224195510804

遇到缺失值不要紧,可以选择填充方法来改善,之后会处理实际数据集的缺失问题,这里只做简单举例:

1
df.fillna(666)

image-20250224195540467

通过fillna()函数可以对缺失值进行填充,这里只选择一个数值,实际中更常使用的是均值、中位数等指标,还需要根据具体问题具体分析。

3.4 apply自定义函数

接下来又是重磅嘉宾出场了,apply()函数可是一个“神器”,如果你想要完成的任务没办法直接实现,就需要使用apply自定义函数功能,还是先来看看其用法:

1
2
data = pd.DataFrame({'food':["A1", "A2", "B1", "B2", "B3", "C1", "C2"], 'data':[1, 2, 3, 4, 5, 6, 7]})
display(data)

image-20250224195755805

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def food_map(series):
    if series['food'] == "A1":
        return "A"
    elif series['food'] == "A2":
        return "A"
    elif series['food'] == "B1":
        return "B"
    elif series['food'] == "B2":
        return "B"
    elif series['food'] == "B3":
        return "B"
    elif series['food'] == "C1":
        return "C"
    elif series['food'] == "C2":
        return "C"
data["food_map"] = data.apply(food_map, axis = "columns")
display(data)

image-20250224200117132

上述操作首先定义了一个映射函数,如果想要改变food列中的所有值,在已经给出映射方法的情况下,如何在数据中执行这个函数,以便改变所有数据呢?是不是要写一个循环来遍历每一条数据呢?肯定不是的,只需调用apply()函数即可完成全部操作。 可以看到,apply()函数使用起来非常简单,需要先写好要执行操作的函数,接下来直接调用即可,相当于对数据中所有样本都执行这样的操作,下面继续拿泰坦尼克号数据来试试apply()函数:

1
2
3
4
5
6
7
def nan_count(columns):
    columns_null = pd.isnull(columns)
    null = columns[columns_null]
    return len(null)

titanic = pd.read_csv("./titanic.csv")
titanic.apply(nan_count)

image-20250224200401706

这里要统计的就是每列的缺失值个数,写好自定义函数之后依旧调用apply()函数,这样每列特征的缺失值个数就统计出来了,再来统计一下每一位乘客是否是成年人:

1
2
3
4
5
6
7
def is_minor(row):
    if row["Age"] < 18:
        return True
    else:
        return False

titanic.apply(is_minor, axis = 1)[:25]   # 只展示前25条数据

image-20250224200637630

使用apply函数在做数据处理时非常便捷,先定义好需要的操作,但是最好先拿部分样本测试一下函数是否正确,然后就可以将它应用在全部数据中了,对行或者对列进行操作都是可以的,相当于自定义一套处理操作。

3.5 时间操作

在机器学习建模中,从始至终都是尽可能多地利用数据所提供的信息,当然时间特征也不例外。当拿到一份时间特征时,最好还是将其转换成标准格式,这样在提取特征时更方便一些:

1
2
ts = pd.Timestamp("2025-02-24")
display(ts)

image-20250224200808709

1
2
3
ts.month
ts.day
ts + pd.Timedelta("5 days")

image-20250224200913298

时间特征只需要满足标准格式就可以调用各种函数和属性了,上述操作通过时间提取了当前具体的年、月、日等指标。

1
2
s = pd.Series(['2025-02-24 00:00:00', '2025-02-25 00:00:00', '2025-02-26 00:00:00'])
display(s)

image-20250224201027067

1
pd.to_datetime(s)

image-20250224201201027

一旦转换成标准格式,注意其dtype类型,就可以调用各种属性进行统计分析了:

1
2
3
s = pd.to_datetime(s)
s.dt.hour
s.dt.weekday

image-20250224201313814

如果数据中没有给定具体的时间特征,也可以自己来创建,例如知道数据的采集时间,并且每条数据都是固定时间间隔保存下来的:

1
pd.Series(pd.date_range(start='2025-02-24', periods=10, freq="12h"))

image-20250224201514560

读取数据时,如果想以时间特征为索引,可以将parse_dates参数设置为True:

1
2
df = pd.read_csv("./flowdata.csv", index_col=0, parse_dates=True)
display(df)

image-20250224201705974

注:index_col=0 表示将 CSV 文件中的第一列(索引从 0 开始)作为 DataFrame 的索引。也就是说,读取 CSV 文件时,第一列的数据不会作为普通数据列,而是被设置为 DataFrame 的行标签。

有了索引后,就可以用它来取数据啦:

1
df[pd.Timestamp("2009-01-01 00:00:00"):pd.Timestamp("2009-01-01 19:00:00")]

image-20250224201857506

1
2
# df['2013']   报错
df.loc['2013']

image-20250224202236727

也用data['2012-01':'2012-03']指定具体月份,或者更细致一些,在小时上继续进行判断,如data[(data.index.hour>8)&(data.index.hour<12)]

1
display(df.loc['2012-01':'2012-03'])

image-20250224203347964

1
df[(df.index.hour>8)&(df.index.hour<12)]

image-20250224203412451

下面再介绍一个重量级的家伙,在处理时间特征时候经常会用到它——resample重采样,先来看看执行结果:

1
2
data = pd.read_csv("./flowdata.csv", index_col=0, parse_dates=True)
data.resample("D").mean().head()

image-20250224203534660

原始数据中每天都有好几条数据,但是这里想统计的是每天的平均指标,当然也可以计算其最大值、最小值,只需把.mean()换成.max()或者.min()即可。

例如想按3天为一个周期进行统计:

1
data.resample("3D").mean().head()

image-20250224203643201

按月进行统计也是同理:

1
data.resample("ME").mean().head()

image-20250224203725831

时间数据可以提取出非常丰富的特征,不仅有年、月、日等常规指标,还可以判断是否是周末、工作日、上下旬、上下班时间、节假日等特征,这些特征对数据挖掘任务都是十分有帮助的。

3.6 绘图操作

如果对数据进行简单绘图也可以直接用Pandas工具包,1行代码就能进行基本展示,但是,如果想把图绘制得更完美一些,还需要使用专门的工具包,例如Matplotlib、Seaborn等,这里先演示Pandas中基本绘图方法:

1
2
3
4
# 在notebook中要绘图需要先执行下面这行
%matplotlib inline
df = pd.DataFrame(np.random.randn(10, 4).cumsum(0), index=np.arange(0, 100, 10), columns = ["A", "B", "C", "D"])
df.plot()

image-20250224204300069

注1:%matplotlib inline是一个 IPython “魔术命令” (magic command),它的作用是在 Jupyter Notebook 或 IPython 环境中,将 matplotlib 生成的图形直接嵌入到 notebook 的输出中,而不是在一个单独的窗口中显示。

  • 这意味着你运行 df.plot() 后,图表会直接显示在代码单元格的下方。
  • 如果你没有运行这个命令,df.plot() 可能会生成一个需要你手动关闭的窗口,或者根本不显示任何东西(取决于你的 matplotlib 配置)。

注2:数据部分创建了一个包含 10 行 4 列数据的 DataFrame,数据是随机生成的,然后计算了每列的累积和,并且设置了一个有意义的索引。

注3:plot() 方法会将 DataFrame 的每一列绘制成一条线,x 轴是 DataFrame 的索引,y 轴是列的值。


虽然直接对数据执行plot()操作就可以完成基本绘制,但是,如果想要加入一些细节,就需要使用Matplotlib工具包(下一章还会专门讲解),例如要同时展示两个图表,就要用到子图:

1
2
3
4
5
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 1)
data = pd.Series(np.random.rand(16), index=list('abcdefghijklmnop'))
data.plot(ax=axes[0], kind="bar")
data.plot(ax=axes[1], kind="barh")   # h代表horizontal水平的

image-20250224204748817

还可以指定绘图的种类,例如条形图、散点图等:

1
2
3
df = pd.DataFrame(np.random.rand(6, 4), index=["one", "two", "three", "four", "five", "six"],
                 columns=pd.Index(["A", "B", "C", "D"], name="Genus"))
display(df)

image-20250224205044741

1
df.plot(kind='bar')

image-20250224205114749

1
2
macro = pd.read_csv("./macrodata.csv")
display(macro)

image-20250224205251581

1
macro.plot.scatter("quarter", "realgdp")

image-20250224205355562

注:从名为 macro 的 Pandas DataFrame 中,选择名为 “quarter” 的列作为 X 轴,选择名为 “realgdp” 的列作为 Y 轴,然后创建一个散点图,显示实际 GDP 随季度变化的关系。 通过观察这个散点图,可以初步了解 GDP 是否有周期性变化,以及在哪些季度增长或下降。

  • scatter: 这是 plot 方法的一个特定参数,告诉 Pandas 创建一个散点图。 散点图用于显示两个变量之间的关系,其中每个数据点都表示为图上的一个点。

这些就是Pandas工具包绘图的基本方法,一般都是在简单观察数据时使用,实际进行分析或者展示还是用Matplotlib工具包更专业一些。