pd.agg() - 在Pandas中聚合数据的方法

327 阅读13分钟

agg 这个名字是聚合的简称。聚合就是将许多观察值汇总成一个单一的值,代表观察数据的某个方面。

.agg() 函数可以处理一个数据框架、一个系列或一个分组的数据框架。它可以执行许多聚合函数,例如:'[mean](https://blog.finxter.com/pandas-dataframe-mean-method/)', '[max](https://blog.finxter.com/pandas-dataframe-max-method/)',......在一次调用中沿着其中一个轴。它还可以执行lambda函数。请继续阅读例子。

我们将使用一个FIFA球员的数据集。在这里可以找到这个数据集。

使用Jupyter笔记本的基本设置

让我们从导入pandas和加载我们的数据集开始。

import pandas as pd
df_fifa_soccer_players = pd.read_csv('fifa_cleaned.csv')
df_fifa_soccer_players.head()

为了提高可读性,我们将使用数据的一个子集。让我们通过选择我们想要在子集中的列来创建子集,并创建一个新的数据框架。

df_fifa_soccer_players_subset = df_fifa_soccer_players[['nationality', 'age', 'height_cm', 'weight_kgs', 'overall_rating', 'value_euro', 'wage_euro']]
df_fifa_soccer_players_subset.head()

基本聚合

Pandas提供了各种内置的聚合函数。例如,pandas.DataFrame.describe 。当应用于一个数据集时,它会返回一个统计值的摘要。

df_fifa_soccer_players_subset.describe()

为了理解聚合以及为什么它有帮助,让我们仔细看看返回的数据。

例子。我们的数据集包含17954名球员的记录。最年轻的球员是17岁,最年长的球员是46岁。平均年龄为25岁。我们了解到,最高的球员身高为205厘米,平均身高为175厘米左右。通过一行代码,我们可以回答关于我们数据的各种统计问题。describe 函数识别了数字列并为我们进行了统计汇总。Describe还排除了包含字符串值的列nationality

聚合就是将许多观察值总结成一个单一的值,代表观察数据的某个方面。

Pandas为我们提供了各种预建的聚合函数。

函数说明
mean()返回一组数值的平均值
sum()返回一组数值的总和
count()返回一组数值的计数
std()返回一组数值的标准偏差
min()返回一组数值中最小的值
max()返回一组数值的最大值
describe()返回一组数值的统计值的集合
size()返回一组数值的大小
first()返回一组数值的第一个值
last()返回一组数值的最后一个数值
nth()返回一组数值的第n个数值
sem()返回一组数值的平均值的标准误差
var()返回一组数值的方差
nunique()返回一组数值的唯一值的数量。

让我们使用上面列表中的另一个函数。我们可以更具体一些,要求 "value_euro"系列的 "sum"。这一列包含一个球员的市场价值。我们选择列或系列 'value_euro' 并执行预先建立的sum() 函数。

df_fifa_soccer_players_subset['value_euro'].sum()
# 43880780000.0

Pandas向我们返回了所要求的值。让我们来了解一个更强大的pandas方法来聚合数据。

pandas.DataFrame.agg' 方法

函数语法

.agg() 函数可以接受许多输入类型。输出类型在很大程度上是由输入类型决定的。我们可以向.agg() 函数传入许多参数。

"func" 参数。

  • 默认设置为 **None**
  • 包含一个或多个聚合数据的函数
  • 支持预先定义的pandas聚合函数
  • 支持lambda表达式
  • 支持 [dataframe.apply()](https://blog.finxter.com/pandas-apply-a-helpful-illustrated-guide/)方法,用于特定的函数调用

"axis" 参数。

  • 默认设置为0 ,将函数应用于每一列
  • 如果设置为1,则将函数应用于行
  • 可以持有值。
    • 0 或 ' 'index
    • 1 或 ' 'columns

那么*args**kwargs:

  • 我们使用这些占位符,如果我们事先不知道我们需要向函数传递多少个参数的话
  • 当参数的类型相同时,我们使用*args
  • 当参数的类型不同时,我们使用**kwargs

系列上的Agg方法

让我们看看.agg() 这个函数的运行情况。我们为'wage_euro'系列请求一些预建的聚合函数。我们使用函数参数并提供我们想要执行的聚合函数作为一个列表。并且让我们把得到的系列保存在一个变量中。

wage_stats = df_fifa_soccer_players_subset['wage_euro'].agg(['sum', 'min', 'mean', 'std', 'max'])
print(wage_stats)

Pandas使用科学符号来表示大大小小的浮点数。为了将输出转换为熟悉的格式,我们必须将浮点数移到右边,如加号所示。加号后面的数字代表了步数的多少。

让我们一起对一些数值进行操作。

所有工资的总和是175,347,000欧元(1.753470e+08)

工资的平均值是9902.135欧元(9.902135e+03)。

我们在一个系列输入源上执行了许多函数。因此,我们的变量 'wage_stats' 属于Series 类型,因为。

type(wage_stats)
# pandas.core.series.Series

请看下面如何从变量和返回的数据类型中提取,例如,'min'值。

wage_stats_min = wage_stats['min']
print(wage_stats_min)
# 1000.0


print(type(wage_stats_min))
# numpy.float64

现在的数据类型是一个标量。

如果我们在同一个数据源(系列)上执行一个函数,返回的类型是一个标量。

wage_stats_max = df_fifa_soccer_players_subset['wage_euro'].agg('max')
print(wage_stats_max)
# 565000.0

print(type(wage_stats_max))
# numpy.float64

让我们再用一个例子来理解输入类型和输出类型之间的关系。

我们将使用函数 "nunique" ,它将给我们提供唯一国籍的计数。让我们在两个代码例子中应用这个函数。我们两次都将引用系列 "nationality"。唯一的区别是我们将函数 "nunique" 传递给我们的agg() 函数的方式。

nationality_unique_series = df_fifa_soccer_players_subset['nationality'].agg({'nationality':'nunique'})
print(nationality_unique_series)
# nationality    160
# Name: nationality, dtype: int64

print(type(nationality_unique_series))
# pandas.core.series.Series

当我们使用字典传入 "nunique" 函数时,输出类型是一个系列。

nationality_unique_int = df_fifa_soccer_players_subset['nationality'].agg('nunique')
print(nationality_unique_int)
# 160

print(type(nationality_unique_int))
# int

当我们将 "nunique" 函数直接传入agg() 时,输出类型是一个整数。

DataFrame 上的 Agg 方法

以Python列表的形式传递聚合函数

一列代表一个系列。我们现在将选择两列作为我们的输入,因此与一个数据框架一起工作。

让我们选择列 'height_cm' 和 'weight_kgs' 。

我们将执行函数min(),mean()max() 。为了选择一个二维数据(数据框架),我们需要使用双括号。我们将把结果四舍五入到两个小数点。

让我们把结果存储在一个变量中。

height_weight = df_fifa_soccer_players_subset[['height_cm', 'weight_kgs']].agg(['min', 'mean', 'max']).round(2)
print(height_weight)

我们得到一个包含行和列的数据框。让我们通过检查'height_weight'变量的类型来确认这一观察。

print(type(height_weight))
# pandas.core.frame.DataFrame

我们现在将使用我们新创建的名为'height_weight'的数据框架来使用'axis'参数。整个数据框架包含数字值。

我们定义函数并传入axis 参数。我使用count()sum() 函数来显示axis 参数的效果。结果数值没有什么意义。这也是我为什么不重命名标题以恢复丢失的列名的原因。

height_weight.agg(['count', 'sum'], axis=1)

我们沿着行进行汇总。返回每行的项目数和项目值的总和。

将聚合函数作为python字典传递

现在让我们将不同的函数应用于我们数据框架中的各个集合。我们选择集合 'overall_rating' 和 'value_euro'。我们将把函数std(),sem()mean() 应用到 'overall_rating' 系列,把函数min()max() 应用到 'value_euro' 系列。

rating_value_euro_dict = df_fifa_soccer_players_subset[['overall_rating', 'value_euro']].agg({'overall_rating':['std', 'sem', 'mean'], 'value_euro':['min', 'max']})
print(rating_value_euro_dict)

该数据框架包含计算值和空(NaN)值。让我们快速确认我们输出的类型。

print(type(rating_value_euro_dict))
# pandas.core.frame.DataFrame

以Python元组的形式传递聚合函数

现在我们将重复前面的例子。

我们将使用元组而不是字典来传入聚合函数。元组有局限性。我们只能在一个元组中传递一个聚合函数。我们还必须为每个元组命名。

rating_value_euro_tuple = df_fifa_soccer_players_subset[['overall_rating', 'value_euro']].agg(overall_r_std=('overall_rating', 'std'),overall_r_sem=('overall_rating', 'sem'),overall_r_mean=('overall_rating', 'mean'),value_e_min=('value_euro', 'min'),value_e_max=('value_euro', 'max'))
print(rating_value_euro_tuple)

在分组的DataFrame上的Agg方法

通过单列进行分组

''[groupby](https://blog.finxter.com/pd-dataframe-groupby-a-simple-illustrated-guide/)'方法创建一个分组数据框架。我们现在将选择列'age'和'wage_euro',并使用列'age'来分组我们的数据框架。在我们的分组数据框架上,我们将使用函数count(),min(),max()mean() 应用agg() 函数。

age_group_wage_euro = df_fifa_soccer_players_subset[['age', 'wage_euro']].groupby('age').aggage(['count', 'min', 'max', 'mean'])
print(age_group_wage_euro)

每一行代表一个年龄组。计数值显示有多少球员属于该年龄组。最小值、最大值和平均值对年龄组成员的数据进行汇总。

多重指数

分组数据框架的另一个方面是产生的分层索引。我们也称它为 多指标.

我们可以看到,我们分组的数据框架的各个列处于不同的层次。查看层次结构的另一种方法是请求特定数据集的列。

print(age_group_wage_euro.columns)

使用多索引的工作是另一篇博文的主题。为了使用我们已经讨论过的工具,让我们平铺多索引并重置索引。我们需要以下函数。

  • droplevel()
  • reset_index()
age_group_wage_euro_flat = age_group_wage_euro.droplevel(axis=1, level=0).reset_index()
print(age_group_wage_euro_flat.head())

结果数据框架的列现在是平的。在扁平化过程中我们丢失了一些信息。让我们重新命名这些列并返回一些丢失的上下文。

age_group_wage_euro_flat.columns = ['age', 'athlete_count', 'min_wage_euro', 'max_wage_euro', 'mean_wage_euro']
print(age_group_wage_euro_flat.head())

按多列分组

按多列分组可以创建更加细化的子段。

让我们使用 "age"作为第一个分组参数,"nationality"作为第二个分组参数。我们将使用列'overall_rating'和'height_cm'来汇总所产生的分组数据。现在我们已经熟悉了这个例子中使用的聚合函数。

df_fifa_soccer_players_subset.groupby(['age', 'nationality']).agg({'overall_rating':['count', 'min', 'max', 'mean'], 'height_cm':['min', 'max', 'mean']})

每个年龄组都包含国籍组。聚合的运动员数据是在国籍组内。

自定义聚合函数

我们可以编写和执行自定义聚合函数来回答非常具体的问题。

让我们来看看内联lambda函数。

💡 Lambda函数是所谓的匿名函数。它们被这样称呼是因为它们没有名字。在一个lambda函数中,我们可以执行多个表达式。我们将通过几个例子来看看lambda函数的作用。

在pandas中,lambda函数存在于 "DataFrame.apply()" 和 "Series.appy()" 方法中。我们将使用DataFrame.appy() 方法来执行两个轴上的函数。让我们先来看看基础知识。

函数语法

DataFrame.apply() 函数将沿着DataFrame的定义轴执行一个函数。我们将在我们的例子中执行的函数将与通过apply() 方法传递给我们的自定义函数的系列对象一起工作。根据我们将选择的轴,系列将由我们的数据框架的一行或一列组成。

"func" 参数。

  • 包含一个应用于数据框架中某一列或某一行的函数。

axis"参数。

  • 默认设置为0 ,将传递一系列的列数据。
  • 如果设置为1将传递一系列的行数据
  • 可以持值。
    • 0或 "index
    • 1或 'columns'

"raw" 参数。

  • 是一个布尔值
  • 缺省设置为 False
  • can hold values:
    • False --一个Series对象被传递给函数
    • True-> 一个ndarray 对象被传递给了函数

"result_type" 参数。

  • 只能在轴为1或 'columns' 时适用。
  • can hold values:
    • 'expand'
    • ‘reduce’
    • 'broadcast'

"args()" 参数。

  • 作为元组的函数的附加参数

**kwargs "参数:函数的附加参数为元组。

  • 参数:函数的附加参数,作为键值对。

过滤器

让我们看一下过滤器。它们在我们探索数据时将非常方便。

在这个代码示例中,我们创建了一个名为filt_rating 的过滤器。我们选择我们的数据框架和列overall_rating 。如果overall_rating 列中的值是90或以上,条件>= 90 返回True

否则,过滤器会返回False

filt_rating = df_fifa_soccer_players_subset['overall_rating'] >= 90
print(filt_rating)

结果是一个Series对象,包含索引,以及相关的值TrueFalse

让我们将过滤器应用于我们的数据框架。我们调用 [.loc](https://blog.finxter.com/slicing-data-from-a-pandas-dataframe-using-loc-and-iloc/)方法,并将过滤器的名称作为一个列表项传入。这个过滤器的工作原理就像一个面具。它涵盖了所有数值为False 的记录。其余的行符合我们的过滤条件:overall_rating >= 90

df_fifa_soccer_players_subset.loc[filt_rating]

Lambda函数

让我们用一个lambda函数来重新创建同样的过滤器。我们将称我们的过滤器为filt_rating_lambda

让我们看一下代码。我们指定我们的过滤器的名称,并调用我们的数据框架。请注意双方括号。我们用它们来传递一个数据框架而不是一个系列对象给.appy() 方法。

.apply() ,我们使用关键字'lambda'来表明我们即将定义我们的匿名函数。'x' 代表传递到lambda函数中的系列。

该系列包含来自overall_rating 列的数据。在半列之后,我们再次使用占位符x 。现在我们应用一个名为ge() 的方法。它代表了我们在第一个过滤器例子中使用的相同条件 ">=" (大于或等于)。

我们定义整数值为90,并关闭应用函数的括号。结果是一个数据框架,它包含一个索引和只有一列的布尔值。为了将这个数据框架转换为一个系列,我们使用squeeze() 方法。

filt_rating_lambda = df_fifa_soccer_players_subset[['overall_rating']].apply(lambda x:x.ge(90)).squeeze()
print(filt_rating_lambda)

让我们使用我们的过滤器。很好,我们得到的结果与我们第一个过滤器的例子相同。

df_fifa_soccer_players_subset.loc[filt_rating_lambda]

我们现在想知道我们的过滤器返回了多少名球员。让我们先不使用lambda函数,然后使用lambda函数来看看同样的结果。我们正在计算行数或记录。

df_fifa_soccer_players_subset.loc[filt_rating_lambda].count()

df_fifa_soccer_players_subset.apply(lambda x:x.loc[filt_rating_lambda]).count()

很好。现在让我们把我们放在一个真正需要使用apply() 方法和lambda函数的地方。我们想在一个分组的数据框中使用我们的过滤器。

让我们按国籍分组,看看这些了不起的球员的分布情况。输出将包含所有列。这使得代码更容易阅读。

df_fifa_soccer_players_subset.groupby('nationality').loc[filt_rating_lambda]

Pandas在这个错误信息中告诉我们,我们不能在分组的数据框对象上使用'loc'方法。

现在让我们看看如何通过使用lambda函数来解决这个问题。我们不在分组数据框架上使用'loc'函数,而是使用apply() 函数。在apply() 函数中,我们定义我们的 lambda 函数。现在我们在变量 "x"上使用 "loc"方法,并通过我们的过滤器。

df_fifa_soccer_players_subset.groupby('nationality').apply(lambda x:x.loc[filt_rating_lambda])

apply()函数的轴参数

现在让我们使用axis 参数来计算这些球员的体重指数(BMI)。到目前为止,我们已经在我们的数据列上使用了lambda函数。

'x'变量是单个列的代表。我们将轴参数设置为'1'。在我们的lambda函数中的'x'变量现在将代表我们数据的各个行。

在我们计算BMI之前,让我们创建一个新的数据框架并定义一些列。我们将称我们的新数据框架为'df_bmi'。

df_bmi = df_fifa_soccer_players_subset.groupby('nationality')[['age', 'height_cm', 'weight_kgs']].apply(lambda x:x.loc[filt_rating_lambda])
print(df_bmi)

现在让我们重设索引。

df_bmi = df_bmi.reset_index()
print(df_bmi)

我们计算BMI的方法如下。我们用体重(公斤)除以身高(米)的平方。

让我们仔细看一下lambda函数。我们将'axis'定义为'1'。'x' 变量现在代表一行。我们需要在每一行使用特定的值。为了定义这些值,我们使用变量 'x' 并指定一个列名。在我们代码示例的开头,我们定义了一个新的列,命名为'bmi'。在最后,我们将结果取整。

df_bmi['bmi'] = df_bmi.apply(lambda x:x['weight_kgs']/((x['height_cm']/100)**2), axis=1).round()
print(df_bmi)

很好!我们的自定义函数成功了。我们的自定义函数起作用了。新的BMI列包含计算值。

总结

恭喜你完成了本教程。我祝愿你在今后的数据项目中能有许多伟大而细微的见解。我包括Jupyter-Notebook文件,所以你可以实验和调整代码。


书呆子的幽默