pandas(2)数据分析

pandas 第二部分:数据分析

Posted by Hilda on February 24, 2025

NumPy 博客总结:

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

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

pandas博客总结:

pandas(1)数据预处理


数据分析处理库Pandas(2)

2 数据分析

在DataFrame中对数据进行计算跟Numpy差不多,例如:

1
2
3
4
5
6
import pandas as pd 
df = pd.read_csv("./titanic_train.csv")
df1 = df.copy()
display(df1[:5]) # 查看前5条
df1['Age'] = df1['Age'] + 10
display(df1[:5]) # 查看前5条

image-20250223194600584

2.1 统计分析

拿到特征之后可以分析的指标比较多,例如均值、最大值、最小值等均可以直接调用其属性获得。先用字典结构创建一个简单的DataFrame,既可以传入数据,也可以指定索引和列名:

1
2
3
4
5
6
# 创建一个DataFrame
df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], index = ['a', 'b'], columns = ["A", "B", "C"])
display(df)

# 默认每一列求和,相当于df.sum(axis = 0)
df.sum()

image-20250223195053219

1
df.sum(axis = 0)

image-20250223195109896

1
2
# 也可以指定维度求和
df.sum(axis = 1)

image-20250223195145984

同理均值df.mean()、中位数df.median()、最大值df.max()、最小值df.min()等操作的计算方式都相同。这些基本的统计指标都可以一个个来分析,但是还有一个更方便的函数能观察所有样本的情况:

1
df.describe()

image-20250223195231565

上述输出展示了泰坦尼克号乘客信息中所有数值特征的统计结果,包括数据个数、均值、标准差、最大值、最小值等信息。这也是读取数据之后最常使用的统计方法。

读取完数据之后使用describe()函数,既可以得到各项统计指标,也可以观察数据是否存在问题,例如年龄的最小值是否存在负数,数据是否存在缺失值等。实际处理的数据不一定完全正确,可能会存在各种问题。

除了可以执行这些基本计算,还可以统计二元属性,例如协方差、相关系数等,这些都是数据分析中重要的指标:

1
2
3
4
5
6
7
df = pd.read_csv("./titanic.csv")
# 注:要从 DataFrame 中删除非数值列(否则无法计算协方差和相关系数)
df = df.select_dtypes(include=['number'])
# 协方差
display(df.cov())
# 相关系数
display(df.corr())

image-20250223200213847

原书此处没有df = df.select_dtypes(include=['number'])这行代码,会报错,因为 Titanic 数据集中包含无法转换为浮点数的字符串列,导致 df.cov() df.corr() 函数无法计算协方差和相关系数。 这些函数只能处理数值数据。

如果还想统计某一列各个属性的比例情况,例如乘客中有多少男性、多少女性,这时候value_counts()函数就可以发挥作用了:

1
2
df = pd.read_csv("./titanic.csv")
df['Sex'].value_counts()

image-20250223200424125

按升序排列结果:也就是说,出现次数最少的性别将排在第一位,出现次数最多的性别将排在最后一位。 默认情况下,ascending=False,这意味着结果会按降序排列(出现次数最多的值排在第一位)。

1
df['Sex'].value_counts(ascending=True)

image-20250223200449168

但是value_counts不是很适合离散的:(例如年龄)

1
df["Age"].value_counts(ascending=True)

image-20250223202945576

如果全部打印,结果实在太多,因为各种年龄的都有,这个时候也可以指定一些区间,例如0~10岁属于少儿组,10~20岁属于青年组,这就相当于将连续值进行了离散化: bins=5 会将年龄范围分割成 5 个大小相等的区间,并统计每个区间内的年龄数量。

1
df["Age"].value_counts(ascending=True, bins=5)

image-20250223203144331

把所有数据按年龄平均分成5组,这样看起来就舒服多了。求符合每组情况的数据各有多少,这些都是在实际数据处理过程中常用的技巧。

在分箱操作中还可以使用cut()函数,功能更丰富一些。首先创建一个年龄数组,然后指定3个判断值,接下来就用这3个值把数据分组,也就是(10,40],(40,80]这两组,返回的结果分别表示当前年龄属于哪组。

1
2
3
ages = [15, 18, 20, 21, 22, 34, 41, 52, 63, 79]
bins = [10, 40, 80]
pd.cut(ages, bins)

image-20250223203440926

也可以打印其默认标签值:

注:原书此处已经运行报错了。在 Pandas 的早期版本中,pd.cut() 返回的 Categorical 对象确实有一个 labels 属性,用于获取每个元素所属的箱子的标签(整数索引)。 但是,这个属性在较新的 Pandas 版本中已被弃用,并被 codes 属性取代。

1
2
3
4
ages = [15, 18, 20, 21, 22, 34, 41, 52, 63, 79]
bins = [10, 40, 80]
bins_res = pd.cut(ages, bins)
bins_res.codes

image-20250223204414053

统计各组总共人数:

1
pd.value_counts(bins_res)

image-20250223204512133

也可以分成三组:年轻人,中年人,老年人

1
pd.cut(ages, [10, 30, 50, 80])

image-20250223204624377

指定每组的标签:

1
2
group_name = ["Youth", "Mille", "Old"]
pd.value_counts(pd.cut(ages, [10, 30, 50, 80], labels = group_name))

image-20250223204851321

机器学习中比拼的就是数据特征够不够好,将特征中连续值离散化可以说是常用的套路。

2.2 pivot数据透视表

下面演示在数据统计分析中非常实用的pivot函数,熟悉的读者可能已经知道它是用来展示数据透视表操作的,说白了就是按照自己的方式来分析数据。

先来创建一份比较有意思的数据,因为一会儿要统计一些指标,数据量要稍微多一点。

1
2
3
4
5
6
7
8
example = pd.DataFrame({'Month': ["January", "January", "January", "January", 
                                  "February", "February", "February", "February", 
                                  "March", "March", "March", "March"],
                   'Category': ["Transportation", "Grocery", "Household", "Entertainment",
                                "Transportation", "Grocery", "Household", "Entertainment",
                                "Transportation", "Grocery", "Household", "Entertainment"],
                   'Amount': [74., 235., 175., 100., 115., 240., 225., 125., 90., 260., 200., 120.]})
display(example)

image-20250223205116342

其中Category表示把钱花在什么用途上(如交通运输、家庭、娱乐等费用),Month表示统计月份,Amount表示实际的花费。

下面要统计的就是每个月花费在各项用途上的金额分别是多少:

1
2
example_pivot = example.pivot(index = "Category", columns = "Month", values="Amount")
display(example_pivot)

image-20250223205325202

这几个月中每项花费的总额:

1
example_pivot.sum(axis=1)

image-20250223205430097

每个月所有花费的总额:

1
example_pivot.sum(axis=0)

image-20250223205453774

上述操作中使用了3个参数,分别是index、columns和values,它们表示什么含义呢?直接解释其含义感觉有点生硬,还是通过例子来观察一下,现在回到泰坦尼克号数据集中,再用pivot函数感受一下:

1
2
3
df = pd.read_csv("./titanic.csv")
df_pivot = df.pivot_table(index = "Sex", columns="Pclass", values="Fare")
display(df_pivot)

注:pivot_table() 函数允许指定一个聚合函数(例如 mean, sum, count)来处理重复的组合。用pivot进行上面的处理会报错。

image-20250223205911539

其中Pclass表示船舱等级,Fare表示船票的价格。这里表示按乘客的性别分别统计各个舱位购票的平均价格。通俗的解释就是,index指定了按照什么属性来统计,columns指定了统计哪个指标,values指定了统计的实际指标值是什么。看起来各项参数都清晰明了,但是平均值从哪里来呢?平均值相当于是默认值,如果想指定最大值或者最小值,还需要额外设置一个计算参数。

1
2
3
df = pd.read_csv("./titanic.csv")
df_pivot = df.pivot_table(index = "Sex", columns="Pclass", values="Fare", aggfunc='max')
display(df_pivot)

image-20250224134003631

aggfunc 是 “aggregate function”(聚合函数)的缩写。pd.pivot_table 函数的 aggfunc 参数的默认值是 ‘mean’。

其他常见的 aggfunc 值包括:

  • ‘mean’:计算平均值。
  • ‘sum’:计算总和。
  • ‘min’:计算最小值。
  • ‘count’:计算非空值的数量。
  • ‘median’:计算中位数。
  • ‘std’:计算标准差。
  • ‘var’:计算方差。

这里得到的结果就是各个船舱的最大票价,需要额外指定aggfunc来明确结果的含义。如果想统计各个船舱等级的人数呢?

1
2
3
df = pd.read_csv("./titanic.csv")
df_pivot = df.pivot_table(index = "Sex", columns="Pclass", values="Fare", aggfunc='count')
display(df_pivot)

image-20250224134734061

接下来做一个稍微复杂点的操作,首先按照年龄将乘客分成两组:成年人和未成年人。再对这两组乘客分别统计不同性别的人的平均获救可能性:

1
2
df["underAged"] = df["Age"] < 18
df.pivot_table(index="underAged", columns="Sex", values="Survived", aggfunc="mean")

image-20250224135008219

看起来是比较麻烦的操作,但在Pandas中处理起来还是比较简单的。

学习过程中可能会遇到有点儿看不懂某些参数解释的情况,最好的方法就是实际试一试,从结果来理解也是不错的选择。

2.3 groupby操作

下面先通过一个小例子解释一下groupby操作的内容:

1
2
3
df = pd.DataFrame({'key':['A','B','C','A','B','C','A','B','C'],
                  'data':[0,5,10,5,10,15,10,15,20]})
df

image-20250224135222995

此时如果想统计各个key中对应的data数值总和是多少,例如key为A时对应3条数据:0、5、10,总和就是15。按照正常的想法,需要把key中所有可能结果都遍历一遍,并且还要求各个key中的数据累加值:


补充:我自己用pivot_table写了个方法:

1
2
3
df = pd.DataFrame({'key':['A','B','C','A','B','C','A','B','C'],
                  'data':[0,5,10,5,10,15,10,15,20]})
df.pivot_table(columns="key", values="data", aggfunc="sum")

image-20250224135913514


1
2
for key in ["A", "B", "C"]:
    print(key, df[df["key"]==key].sum())

image-20250224140026550

这种统计需求是很常见的,那么,有没有更简单的方法呢?这回就轮到groupby登场了:

1
df.groupby("key").sum()

image-20250224140104243

是不是很轻松地就完成了上述任务?统计的结果是其累加值,当然,也可以换成均值等指标:

1
2
import numpy as np
df.groupby("key").aggregate(np.mean)

image-20250224140203627

上面代码也可以写成:

1
df.groupby("key").mean()

image-20250224140251651

pivot_table当然也可以:

1
df.pivot_table(index="key", values="data", aggfunc="mean")

image-20250224141827261

继续回到泰坦尼克号数据集中,下面要计算的是按照不同性别统计其年龄的平均值,所以要用groupby计算一下性别:

1
2
df = pd.read_csv("./titanic.csv")
df.groupby('Sex')["Age"].mean()

image-20250224142031077

结果显示乘客中所有女性的平均年龄是27.91,男性平均年龄是30.72,只需一行就完成了统计工作。groupby()函数中还有很多参数可以设置,再深入了解一下:

1
2
3
4
5
6
7
8
9
import pandas as pd
import numpy as np
df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
                           'foo', 'bar', 'foo', 'foo'],
                   'B' : ['one', 'one', 'two', 'three',
                          'two', 'two', 'one', 'three'],
                   'C' : np.random.randn(8),
                   'D' : np.random.randn(8)})
df

image-20250224142541748

此时想观察groupby某一列后结果的数量,可以直接调用count()属性:

1
2
grouped = df.groupby("A")
grouped.count()

image-20250224142709311

结果中3和5分别对应了原始数据中样本的个数,可以亲自来数一数。这里不仅可以指定一个groupby 对象,指定多个也是没问题的:

1
2
grouped = df.groupby(["A", "B"])
grouped.count()

image-20250224142753623

指定好操作对象之后,通常还需要设置一下计算或者统计的方法,比如求和操作:

1
2
grouped = df.groupby(["A", "B"])
grouped.aggregate(np.sum)

image-20250224150944698

注:因为数据有随机生成的,所以统计结果不同也有可能。

此处的索引就是按照传入参数的顺序来指定的,如果习惯用数值编号索引也是可以的,只需要加入as_index参数:

1
2
grouped = df.groupby(["A", "B"], as_index=False)
grouped.aggregate(np.sum)

image-20250224151430935

groupby操作之后仍然可以使用describe()方法来展示所有统计信息,这里只展示前5条:

1
grouped.describe().head()

image-20250224151953906

看起来统计信息有点多,当然也可以自己设置需要的统计指标:

1
2
grouped = df.groupby("A")
grouped["C"].agg([np.sum, np.mean, np.std])

image-20250224153544701

在groupby操作中还可以指定操作的索引(也就是level),还是通过小例子来观察一下:

注:在下面例子中,可以使用 MultiIndex 来表示不同类别(’bar’, ‘baz’, ‘foo’, ‘qux’)下的子类别(’one’, ‘two’)。

1
2
3
4
arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
          ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"])
index

image-20250224153955105

  • 每个元素都是一个元组,包含了两层索引的值,例如 (‘bar’, ‘one’)。
  • names 属性表示每个索引层级的名称。

这段代码创建了一个 Pandas MultiIndex,它是一个具有两层索引的结构。 第一层索引的名称是 “first”,取值为 ‘bar’, ‘baz’, ‘foo’, ‘qux’;第二层索引的名称是 “second”,取值为 ‘one’, ‘two’。 MultiIndex 可以方便地用于 Pandas DataFrame 或 Series 中,以处理具有层级结构的数据。

这里设置了多重索引,并且分别指定了名字,光有索引还不够,还需要具体数值,接下来可以按照索引进行groupby操作:

1
2
s = pd.Series(np.random.randn(8), index=index)
s

image-20250224154231526

1
2
grouped = s.groupby(level=0)
grouped.sum()

image-20250224154322601

1
2
grouped = s.groupby(level=1)
grouped.sum()

image-20250224154348205

通过level参数可以指定以哪项为索引进行计算。当level为0时,设置名为first的索引;当level为1时,设置名为second的索引。如果大家觉得指定一个数值不够直观,也可以直接用具体名字,结果相同:

1
2
grouped = s.groupby(level="first")
grouped.sum()

image-20250224154425288

groupby函数是统计分析中经常使用的函数,用法十分便捷,可以指定的参数也比较多,但是也非常容易出错,使用时一定先明确要得到的结果再去选择合适的参数。