聚合是指从数组生成标量值的数据转换。上一次学习的代码示例使用了其中几个聚合函数,包括 mean、count、min 和 sum。常见的聚合见下图列表,但是,不仅限于列表中的这组方法。在 GroupBy 对象上调用聚合函数(例如: mean) 时会发生了什么,下面会学习了解。
可以使用自己的设计的聚合方法,也可以调用分组的对象上定义的任何方法。
以上次学习记录中的DataFrame对象df作为操作数据开始学习:
import numpy as np
import pandas as pdnp.random.seed(12345)
df = pd.DataFrame({"key1" : ["a", "a", None, "b", "b", "a", None],"key2" : pd.Series([1, 2, 1, 2, 1, None, 1], dtype="Int64"),"data1" : np.random.standard_normal(7),"data2" : np.random.standard_normal(7)})print(df)
df输出:
key1 | key2 | data1 | data2 | |
---|---|---|---|---|
0 | a | 1 | -0.204708 | 0.281746 |
1 | a | 2 | 0.478943 | 0.769023 |
2 | None | 1 | -0.519439 | 1.246435 |
3 | b | 2 | -0.555730 | 1.007189 |
4 | b | 1 | 1.965781 | -1.296221 |
5 | a | <NA> | 1.393406 | 0.274992 |
6 | None | 1 | 0.092908 | 0.228913 |
下面对df按键key1进行切片,然后对每个切片调用nsmallest(求最小值),并将结果组装到最后的结果对象中:
import numpy as np
import pandas as pdnp.random.seed(12345)
df = pd.DataFrame({"key1" : ["a", "a", None, "b", "b", "a", None],"key2" : pd.Series([1, 2, 1, 2, 1, None, 1], dtype="Int64"),"data1" : np.random.standard_normal(7),"data2" : np.random.standard_normal(7)})grouped = df.groupby("key1")
result = grouped["data1"].nsmallest(2)
print(result)
输出结果:
我们可以使用自己定义的聚合函数,使用方法是将函数传递给 aggregate() 方法或它的短别名 agg()方法,例如:
import numpy as np
import pandas as pd# 自定义聚合函数,返回数组或列表中最大值和最小值的差
def max_to_min(arr):return arr.max() - arr.min()np.random.seed(12345)
df = pd.DataFrame({"key1" : ["a", "a", None, "b", "b", "a", None],"key2" : pd.Series([1, 2, 1, 2, 1, None, 1], dtype="Int64"),"data1" : np.random.standard_normal(7),"data2" : np.random.standard_normal(7)})grouped = df.groupby("key1")
result = grouped.agg(max_to_min)
print(result)
输出结果:
key2 | data1 | data2 | |
---|---|---|---|
key1 | |||
a | 1 | 1.598113 | 0.494031 |
b | 1 | 2.521511 | 2.303410 |
我们会发现,虽然一些统计描述方法严格上来说不是聚合函数,但是也可以使用(如 describe):
import numpy as np
import pandas as pd# 自定义聚合函数,返回数组或列表中最大值和最小值的差
def max_to_min(arr):return arr.max() - arr.min()np.random.seed(12345)
df = pd.DataFrame({"key1" : ["a", "a", None, "b", "b", "a", None],"key2" : pd.Series([1, 2, 1, 2, 1, None, 1], dtype="Int64"),"data1" : np.random.standard_normal(7),"data2" : np.random.standard_normal(7)})grouped = df.groupby("key1")
result = grouped.describe()
print(result)
输出结果:
key2 | data1 | data2 | |||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | mean | std | min | 25% | 50% | 75% | max | count | mean | ... | 75% | max | count | mean | std | min | 25% | 50% | 75% | max | |
key1 | |||||||||||||||||||||
a | 2.0 | 1.5 | 0.707107 | 1.0 | 1.25 | 1.5 | 1.75 | 2.0 | 3.0 | 0.555881 | ... | 0.936175 | 1.393406 | 3.0 | 0.441920 | 0.283299 | 0.274992 | 0.278369 | 0.281746 | 0.525384 | 0.769023 |
b | 2.0 | 1.5 | 0.707107 | 1.0 | 1.25 | 1.5 | 1.75 | 2.0 | 2.0 | 0.705025 | ... | 1.335403 | 1.965781 | 2.0 | -0.144516 | 1.628757 | -1.296221 | -0.720368 | -0.144516 | 0.431337 | 1.007189 |
2 rows × 24 columns
注意:自定义聚合函数通常比上面列表中的优化函数慢得多。这是因为在构建中间组数据块时有一些额外的开销(函数调用、数据重新排列)。
按列进行聚合统计操作
使用上次的tip.csv文件中的数据,还是用代码示例学习更直观:
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
# 输出tips.csv前5行数据
print(tips.head())# 给tips添加一列tip_pct,这一列数据是小费占该次消费金额的百分比
tips["tip_pct"] = tips["tip"] / tips["total_bill"]
# 输出添加tip_pct列后的前5行
print(tips.head())# 按日期和吸烟者对tips进行分组
grouped = tips.groupby(["day", "smoker"])
grouped_pct = grouped["tip_pct"]result = grouped_pct.agg("mean")
print(result)
以上代码按day和smoker列名进行分组,并用agg传递聚合函数mean求每个组的小费百分比的平均值,输出结果:
如果我们传给agg一个聚合函数名称的列表,最后结果会输出一个DataFrame,列名称是函数名,例如:
import numpy as np
import pandas as pd# 自定义聚合函数,返回数组或列表中最大值和最小值的差
def max_to_min(arr):return arr.max() - arr.min()tips = pd.read_csv("examples/tips.csv")
# 输出tips.csv前5行数据
print(tips.head())# 给tips添加一列tip_pct,这一列数据是小费占该次消费金额的百分比
tips["tip_pct"] = tips["tip"] / tips["total_bill"]
# 输出添加tip_pct列后的前5行
print(tips.head())# 按日期和吸烟者对tips进行分组
grouped = tips.groupby(["day", "smoker"])
grouped_pct = grouped["tip_pct"]result = grouped_pct.agg(["mean", "std", max_to_min])
print(result)
输出结果:
mean | std | max_to_min | ||
---|---|---|---|---|
day | smoker | |||
Fri | No | 0.151650 | 0.028123 | 0.067349 |
Yes | 0.174783 | 0.051293 | 0.159925 | |
Sat | No | 0.158048 | 0.039767 | 0.235193 |
Yes | 0.147906 | 0.061375 | 0.290095 | |
Sun | No | 0.160113 | 0.042347 | 0.193226 |
Yes | 0.187250 | 0.154134 | 0.644685 | |
Thur | No | 0.160298 | 0.038774 | 0.193350 |
Yes | 0.163863 | 0.039389 | 0.151240 |
我们也可以传递一个 (name, function) 元组列表,每个元组的第一个元素将用作 DataFrame 列名(可以将 2 元组的列表视为有序映射) ,例如:
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
# 输出tips.csv前5行数据
print(tips.head())# 给tips添加一列tip_pct,这一列数据是小费占该次消费金额的百分比
tips["tip_pct"] = tips["tip"] / tips["total_bill"]
# 输出添加tip_pct列后的前5行
print(tips.head())# 按日期和吸烟者对tips进行分组
grouped = tips.groupby(["day", "smoker"])
grouped_pct = grouped["tip_pct"]result = grouped_pct.agg([("average", "mean"), ("stdev", "std")])
print(result)
输出结果:
average | stdev | ||
---|---|---|---|
day | smoker | ||
Fri | No | 0.151650 | 0.028123 |
Yes | 0.174783 | 0.051293 | |
Sat | No | 0.158048 | 0.039767 |
Yes | 0.147906 | 0.061375 | |
Sun | No | 0.160113 | 0.042347 |
Yes | 0.187250 | 0.154134 | |
Thur | No | 0.160298 | 0.038774 |
Yes | 0.163863 | 0.039389 |
通过使用 DataFrame,我们可以指定要应用于所有列的函数列表或每个列的不同函数。再看一个里例子,假设我们要为 tip_pct 和 total_bill 列计算相同的三个统计信息,可以这样:
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
# 输出tips.csv前5行数据
print(tips.head())# 给tips添加一列tip_pct,这一列数据是小费占该次消费金额的百分比
tips["tip_pct"] = tips["tip"] / tips["total_bill"]
# 输出添加tip_pct列后的前5行
print(tips.head())# 按日期和吸烟者对tips进行分组
grouped = tips.groupby(["day", "smoker"])functions = ["count", "mean", "max"]result = grouped[["tip_pct", "total_bill"]].agg(functions)
print(result)
输出结果:
tip_pct | total_bill | ||||||
---|---|---|---|---|---|---|---|
count | mean | max | count | mean | max | ||
day | smoker | ||||||
Fri | No | 4 | 0.151650 | 0.187735 | 4 | 18.420000 | 22.75 |
Yes | 15 | 0.174783 | 0.263480 | 15 | 16.813333 | 40.17 | |
Sat | No | 45 | 0.158048 | 0.291990 | 45 | 19.661778 | 48.33 |
Yes | 42 | 0.147906 | 0.325733 | 42 | 21.276667 | 50.81 | |
Sun | No | 57 | 0.160113 | 0.252672 | 57 | 20.506667 | 48.17 |
Yes | 19 | 0.187250 | 0.710345 | 19 | 24.120000 | 45.35 | |
Thur | No | 45 | 0.160298 | 0.266312 | 45 | 17.113111 | 41.19 |
Yes | 17 | 0.163863 | 0.241255 | 17 | 19.190588 | 43.11 |
如上输出,生成的 DataFrame 具有分层列,这与我们单独聚合每个列并使用 concat 将结果粘合在一起(使用列名作为 keys 参数)相同,我们单独将 result["tip_pct"]筛选出来看看:
count | mean | max | ||
---|---|---|---|---|
day | smoker | |||
Fri | No | 4 | 0.151650 | 0.187735 |
Yes | 15 | 0.174783 | 0.263480 | |
Sat | No | 45 | 0.158048 | 0.291990 |
Yes | 42 | 0.147906 | 0.325733 | |
Sun | No | 57 | 0.160113 | 0.252672 |
Yes | 19 | 0.187250 | 0.710345 | |
Thur | No | 45 | 0.160298 | 0.266312 |
Yes | 17 | 0.163863 | 0.241255 |
和上面一样,可以传递具有自定义名称的 Tuples 列表:
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
# 输出tips.csv前5行数据
print(tips.head())# 给tips添加一列tip_pct,这一列数据是小费占该次消费金额的百分比
tips["tip_pct"] = tips["tip"] / tips["total_bill"]
# 输出添加tip_pct列后的前5行
print(tips.head())# 按日期和吸烟者对tips进行分组
grouped = tips.groupby(["day", "smoker"])
ftuples = [("Average", "mean"), ("Variance", "var")]result = grouped[["tip_pct", "total_bill"]].agg(ftuples)
print(result)
输出结果:
tip_pct | total_bill | ||||
---|---|---|---|---|---|
Average | Variance | Average | Variance | ||
day | smoker | ||||
Fri | No | 0.151650 | 0.000791 | 18.420000 | 25.596333 |
Yes | 0.174783 | 0.002631 | 16.813333 | 82.562438 | |
Sat | No | 0.158048 | 0.001581 | 19.661778 | 79.908965 |
Yes | 0.147906 | 0.003767 | 21.276667 | 101.387535 | |
Sun | No | 0.160113 | 0.001793 | 20.506667 | 66.099980 |
Yes | 0.187250 | 0.023757 | 24.120000 | 109.046044 | |
Thur | No | 0.160298 | 0.001503 | 17.113111 | 59.625081 |
Yes | 0.163863 | 0.001551 | 19.190588 | 69.808518 |
现在,如果我们想将不同的函数应用于一个或多个列,那我们可以传递一个字典给agg,其中字典包含的是列名称到函数的映射:
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
# 输出tips.csv前5行数据
print(tips.head())# 给tips添加一列tip_pct,这一列数据是小费占该次消费金额的百分比
tips["tip_pct"] = tips["tip"] / tips["total_bill"]
# 输出添加tip_pct列后的前5行
print(tips.head())# 按日期和吸烟者对tips进行分组
grouped = tips.groupby(["day", "smoker"])
result = grouped.agg({"tip" : "max", "size" : "sum"})
result2 = grouped.agg({"tip_pct" : ["min", "max", "mean", "std"], "size" : "sum"})
print(result, '\n', result2)
result输出结果:
tip | size | ||
---|---|---|---|
day | smoker | ||
Fri | No | 3.50 | 9 |
Yes | 4.73 | 31 | |
Sat | No | 9.00 | 115 |
Yes | 10.00 | 104 | |
Sun | No | 6.00 | 167 |
Yes | 6.50 | 49 | |
Thur | No | 6.70 | 112 |
Yes | 5.00 | 40 |
result2输出结果:
tip_pct | size | |||||
---|---|---|---|---|---|---|
min | max | mean | std | sum | ||
day | smoker | |||||
Fri | No | 0.120385 | 0.187735 | 0.151650 | 0.028123 | 9 |
Yes | 0.103555 | 0.263480 | 0.174783 | 0.051293 | 31 | |
Sat | No | 0.056797 | 0.291990 | 0.158048 | 0.039767 | 115 |
Yes | 0.035638 | 0.325733 | 0.147906 | 0.061375 | 104 | |
Sun | No | 0.059447 | 0.252672 | 0.160113 | 0.042347 | 167 |
Yes | 0.065660 | 0.710345 | 0.187250 | 0.154134 | 49 | |
Thur | No | 0.072961 | 0.266312 | 0.160298 | 0.038774 | 112 |
Yes | 0.090014 | 0.241255 | 0.163863 | 0.039389 | 40 |
注意:仅当将多个函数应用于至少一列时,DataFrame 才会具有分层列。
返回不带行索引的聚合数据
在到目前为止的所有示例中,聚合数据都返回了一个索引,该索引可能是分层的,由唯一的组键组合而成。如果不需要索引,我们可以通过将 as_index=False 传递给 groupby 来禁用此行为:
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")# 给tips添加一列tip_pct,这一列数据是小费占该次消费金额的百分比
tips["tip_pct"] = tips["tip"] / tips["total_bill"]result = tips.groupby(["day", "smoker"], as_index=False).mean()
print(result)
输出结果:
day | smoker | total_bill | tip | size | tip_pct | |
---|---|---|---|---|---|---|
0 | Fri | No | 18.420000 | 2.812500 | 2.250000 | 0.151650 |
1 | Fri | Yes | 16.813333 | 2.714000 | 2.066667 | 0.174783 |
2 | Sat | No | 19.661778 | 3.102889 | 2.555556 | 0.158048 |
3 | Sat | Yes | 21.276667 | 2.875476 | 2.476190 | 0.147906 |
4 | Sun | No | 20.506667 | 3.167895 | 2.929825 | 0.160113 |
5 | Sun | Yes | 24.120000 | 3.516842 | 2.578947 | 0.187250 |
6 | Thur | No | 17.113111 | 2.673778 | 2.488889 | 0.160298 |
7 | Thur | Yes | 19.190588 | 3.030000 | 2.352941 | 0.163863 |
当然,通过对结果调用 reset_index 方法也可以实现去除索引,但是会需要一些计算,因此在分组的时候使用 as_index=False 参数可避免一些不必要的计算。