七、数据聚合和分组运算

数据聚合分组运算是数据分析中非常重要的方法。这些方法能够根据指定的关键字将数据拆分成一组组,然后对分组的数据应用一些groupby操作(聚合或转换)来产生一组新的值。然后将结果值组合成一个数据组。

这种方法通常被称为。这个术语实际上是哈德利·韦翰创造的,他是许多流行的“T3”R“T4”包的作者,用来描述分组运算。图 7.1 用图形描述了拆分-应用-合并的思想:

Figure 7.1 – groupby illustration

图 7.1–分组图解

在本章中,我们将探讨执行分组操作的方法:如何按列键对数据进行分组,以及如何联合或独立地对分组的数据进行数据聚合。

本章还将展示如何按键访问分组数据。它还提供了如何为数据创建自定义聚合函数的见解。

本章包含以下主题:

  • 分组数据
  • 遍历分组数据
  • 使用.apply方法
  • 分组数据的数据聚合

技术要求

为了遵循本章,您应该具备以下条件:

  • 现代浏览器,如 Chrome、Safari、Opera 或火狐
  • 系统上安装的 Node.jsDanfo.jsDnotebook

本章代码可在此处获得:https://github . com/PacktPublishing/Building-Data-Driven-Applications-with-danfo . js/tree/main/chapter 07

分组数据

Danfo.js 只提供了功能,通过特定列中的值对数据进行分组。对于当前版本的 Danfo.js,指定的分组列数只能是一个或两个。

在本节中,我们将展示如何按单列和双列分组。

单列分组

首先,让我们从创建一个数据帧开始,然后按单个列对其进行分组:

let data = { 'A': [ 'foo', 'bar', 'foo', 'bar',
                    'foo', 'bar', 'foo', 'foo' ],
                     'C': [ 1, 3, 2, 4, 5, 2, 6, 7 ],
            'D': [ 3, 2, 4, 1, 5, 6, 7, 8 ] };
let df = new dfd.DataFrame(data);
let group_df = df.groupby([ "A"]);

前面的代码涉及以下步骤:

  1. 首先,我们使用object方法创建一个数据帧。
  2. 然后我们调用groupby方法。
  3. 然后,我们指定数据帧应该按列A分组。

df.groupby(['A'])返回一个groupby数据结构,其中包含对数据进行分组所需的所有必要方法。

我们可以决定对所有按A分组的列执行数据操作,或者指定任何其他列。

上述代码输出下表:

Figure 7.2 – DataFrame

图 7.2–数据帧

在下面的代码中,我们将看到如何对分组数据执行一些常见的groupby操作:

group_df.mean().print()

使用前面代码片段中创建的groupby_df操作,我们调用groupby mean方法。此方法计算每组的平均值,如下图所示:

Figure 7.3 – groupby DataFrame

图 7.3–group by 数据框

下图以图形方式显示了前面代码的操作,以及如何生成前面的表输出:

Figure 7.4 – Graphical depiction of the groupby method

图 7.4–分组方法的图形描述

基于我们在本章开头讨论的拆分-应用-组合方法,df.groupby(['A'])将数据框分成两个键–foobar

将数据框分组到foobar键中后,其他列(CD中的值将根据它们的行对齐方式分别分配给这些键中的每一个。要钉住这一点,如果我们点一下bar键,从图 7.2 可以看到C列在bar行有三个数据点(342)。

因此,如果我们要执行数据聚合操作,例如计算分配给bar键的数据的平均值,则属于分配给bar键的列C的数据点将具有3的平均值,其对应于图 7.3 中的表。

注意

foo键进行与上一段所述相同的操作,并分配所有其他数据点

如前所述,对组均值方法的调用适用于所有按A分组的列。让我们选择一个列,我们希望对其专门应用组操作,如下所示:

let col_c = group_df.col(['C'])
col_c.sum().print()

首先,我们调用groupby中的col方法。这个方法接受一个列名数组。前面代码的主要目的是获得每个分组键(foobar)的列C之和,这给出了下表输出:

Figure 7.5 – The groupby operation on column C

图 7.5–C 列上的分组操作

相同的操作可以应用于分组数据中的任何其他列,如下所示:

let col_d = group_df.col(['D'])
col_d.count().print()

该代码片段遵循与前面代码相同的方法,只是将count groupby操作应用于列D,这为我们提供了以下输出:

Figure 7.6 – The groupby "count" operation on column D

图 7.6–D 列上的分组“计数”操作

数据框也可以按两列分组;为单列分组显示的操作也适用于两列分组,我们将在下一小节中看到。

双柱分组

首先,让我们创建一个数据框,并添加一个额外的列,如下所示:

let data = { 'A': [ 'foo', 'bar', 'foo', 'bar',
                    'foo', 'bar', 'foo', 'foo' ],
              ' B': [ 'one', 'one', 'two', 'three',
                  'two', 'two', 'one', 'three' ],
               'C': [ 1, 3, 2, 4, 5, 2, 6, 7 ],
               'D': [ 3, 2, 4, 1, 5, 6, 7, 8 ] };
let df = new dfd.DataFrame(data);
let group_df = df.groupby([ "A", "B"]);

我们增加了一个额外的列B,它包含分类数据–onetwothree。数据框按AB列分组,如下表所示:

Figure 7.7 – DataFrame including column B

图 7.7–包括列 B 的数据帧

我们也可以用单列分组计算平均值,如下所示:

group_df.mean().print()

这基于列AB的组键对列CD应用平均值。比如在图 7.7 中,我们可以看到我们有一个来自AB列的组合键,命名为(fooone)。该键在数据中出现两次。

上述代码输出下表:

Figure 7.8 – The groupby mean of the groupby A and B DataFrame

图 7.8–A 组和 B 组数据帧的组平均值

图 7.7 中,C列有属于(fooone)键的值16。此外,D的值37属于同一个键。如果取平均值,我们会看到它对应于图 7.8 中的第一列。

我们还可以通过列AB获得其中一列的总和。咱们选栏目C:

let col_c = group_df.col(['C']);
col_c.sum().print();

我们从分组数据中获取C列,然后计算每个组键的总和,如下表所示:

Figure 7.9 – Sum of column C per group

图 7.9–每组 C 列的总和

在本节中,我们看到了如何通过单列或双列对数据进行分组。我们还研究了执行数据聚合和访问分组数据帧的列数据。在下一节中,我们将研究如何访问每个分组键的分组数据。

迭代分组数据

在这一节,我们将看到如何基于分组关键字访问分组数据,循环遍历这些分组数据,并对其执行数据聚合操作。

遍历单列和双列分组数据

在本节中,我们将看到 danfo . js 如何提供方法来遍历在groupby操作期间创建的每个组。该数据按包含在groupby列中的键分组。

密钥作为字典或对象存储在名为data_tensors的类属性中。该对象包含分组键作为其键,并且还将与键相关联的数据帧数据存储为对象值。

使用前面的数据框,让我们按列A分组,然后遍历data_tensors:

let group_df = df.groupby([ "A"]);
console.log(group_df.data_tensors)

我们将数据帧按列A分组,然后打印出data_tensors,,如下图所示:

Figure 7.10 – data_tensors output

图 7.10–data _ tensors 输出

图 7.10 包含了更多关于data_tensors属性包含什么的详细信息,但整个内容可以总结为以下结构:

{
  foo: DataFrame,
  bar: DataFrame
}

键是列A的值,键的值是与这些键相关联的数据帧。

我们可以迭代data_tensors并打印出DataFrame表,看看它们包含什么,如下代码所示:

let grouped_data = group_df.data_tensors;
for (let key in grouped_data) {
  grouped_data[key].print();
}

首先,我们访问data_tensors,这是一个groupby类属性,并将其分配给一个名为grouped_data的变量。然后我们循环通过grouped_data,访问它的每个键,并将它们对应的数据框打印为表格,如下图所示:

Figure 7.11 – groupby keys and their DataFrame

图 7.11–group by 键及其数据帧

此外,我们可以将这种相同的方法应用于按两列分组的数据,如前面的代码所示:

let group_df = df.groupby([ "A", "B"]); 
let grouped_data = group_df.data_tensors;
for (let key in grouped_data) {
  let key_data = grouped_data[key];
  for (let key2 in key_data) {
    grouped_data[key][key2].print();
  }
}

下面是我们在前面的代码片段中遵循的步骤:

  1. 首先,df数据框分为两列(AB)。
  2. 我们将data_tensors赋给一个名为grouped_data的变量。
  3. 我们循环通过grouped_data来获得密钥。
  4. We loop through the grouped_data object and also loop through its inner object (key_data) per key, due to the object data format generated for grouped_data, as shown:

    js {   foo : {     one: DataFrame,     two: DataFrame,     three: DataFrame   },   bar: {     one: DataFrame,     two: DataFrame,     three: DataFrame   } }

    代码片段给出了以下输出:

Figure 7.12 – The output of a two-column grouping of a DataFrame

图 7.12–数据帧的两列分组输出

在这一节中,我们介绍了如何迭代分组数据。我们看到了data_tensor的对象格式是如何根据数据的分组方式而变化的,无论是单列还是双列。

我们看到了如何迭代data_tensor来获取密钥及其关联的数据。在下一小节中,我们将看到如何在不手动循环data_tensor的情况下获取与每个按键相关的数据。

使用 get_groups 方法

Danfo.js 提供了一个名为get_groups()的方法,该方法无需遍历data_tensors对象即可轻松访问每个键值数据帧。每当我们需要访问属于一组组合键的特定数据时,这都很方便。

让我们从单列分组开始,如下所示:

let group_df = df.groupby([ "A"]);
group_df.get_groups(["foo"]).print()

我们按列A分组,然后调用get_groups方法。get_groups方法将组合键作为一个数组。

对于单列分组,我们只有一个组合键。因此,我们传入一个名为foo的键,然后打印出相应的分组数据,如下图所示:

Figure 7.13 – get_groups of the foo key

图 7.13–foo 键的 get _ groups

此外,同样的事情可以应用于所有其他键,如下所示:

group_df.get_groups(["bar"]).print()

前面的代码给出了以下输出:

Figure 7.14 – get_groups for the bar key

图 7.14–条形键的 get _ groups

与单列相同的方法groupby也适用于两列分组:

let group_df = df.groupby([ "A", "B"]);
group_df.get_groups(["foo","one"]).print()

请记住get_groups方法将键的组合作为一个数组。因此,对于两列分组,我们传入我们想要的列AB的键组合。因此,我们获得以下输出:

Figure 7.15 – Obtaining a DataFrame for the foo key and one combination

图 7.15–获取 foo 键和一个组合的数据帧

对任何其他组合键都可以做同样的事情。让我们试试bartwo键,如下所示:

group_df.get_groups(["bar","two"]).print()

我们获得以下输出:

Figure 7.16 – DataFrame for the bar and two keys

图 7.16–条形和两个键的数据框

在本节中,我们介绍了如何迭代分组数据,即按单列或双列分组的数据。我们还看到了内部data_tensor数据对象是如何根据数据的分组方式进行格式化的。我们还看到了如何在不循环的情况下访问与每个分组键相关联的数据。

在下一节中,我们还将研究使用.apply方法创建自定义数据聚合函数。

使用。运用方法

在本节中,我们将使用.apply方法来创建可应用于分组数据的自定义数据聚合函数。

.apply方法使自定义函数能够应用于分组数据。这是本章前面讨论的拆分-应用-合并方法的主要功能。

在 Danfo.js 中实现的groupby方法只包含一小组分组数据所需的数据聚合方法,因此.apply方法为用户提供了从分组数据构建特殊数据聚合方法的能力。

使用前面的数据,我们将创建一个新的数据框,不包括前面数据框中看到的列B,然后创建一个将应用于分组数据的自定义函数:

let group_df = df.groupby([ "A"]);
const add = (x) => {
  return x.add(2);
};
group_df.apply(add).print();

在前面的代码中,我们按列A对数据框进行分组,然后创建一个名为add的自定义函数,将值2添加到分组数据中的所有数据点。

注意

要传递到此函数中的参数add和类似的函数,如submuldiv,可以是数据框、序列、数组或整数值。

上述代码生成以下输出:

Figure 7.17 – Applying a custom function to group data

图 7.17–将自定义函数应用于分组数据

让我们创建另一个自定义函数,从每个组值中减去每个分组数据的最小值:

let data = { 'A': [ 'foo', 'bar', 'foo', 'bar',
                    'foo', 'bar', 'foo', 'foo' ],
            'B': [ 'one', 'one', 'two', 'three',
                  'two', 'two', 'one', 'three' ],
            'C': [ 1, 3, 2, 4, 5, 2, 6, 7 ],
            'D': [ 3, 2, 4, 1, 5, 6, 7, 8 ] };
let df = new DataFrame(data);
let group_df = df.groupby([ "A", "B"]);

const subMin = (x) => {
  return x.sub(x.min());
};

group_df.apply(subMin).print();

首先,我们创建了一个包含列ABCD的数据框。然后,我们按列AC对数据框进行分组。

创建一个名为subMin的自定义函数来接收分组数据,获取分组数据的最小值,并从分组数据中的每个数据点减去最小值。

该自定义函数然后通过.apply方法应用于group_df分组数据,因此我们获得以下输出表:

Figure 7.18 – The subMin custom apply function

图 7.18–subMin 自定义应用功能

如果我们看一下上图中的表格,我们可以看到有些组数据只出现一次,比如属于bartwobaronebarthree键、foothree键的组数据。

属于上一个关键字的组数据只有一个项目,因此最小值也是组中包含的单个值;因此,C_applyD_apply列的值为0

如果键对有多行,我们可以调整subMin自定义函数,只从每个值中减去最小值,如下所示:

const subMin = (x) => {
  if (x.values.length > 1) {
    return x.sub(x.min());
  } else {
    return x;
  }
};

group_df.apply(subMin).print();

自定义功能为提供了如下输出表:

Figure 7.19 – Custom apply function

图 7.19–自定义应用功能

下图显示了前面代码的图形表示:

Figure 7.20 – groupby and subMin apply method illustration

图 7.20–group by 和 subMin 应用方法图解

.apply方法也使我们能够对每组数据执行数据标准化过程。

在机器学习中,我们有我们所说的标准化,它涉及在-1 和 1 之间重新调整数据。这个标准化过程包括从数据中减去数据的平均值,然后除以标准偏差。

使用前面的数据框,让我们创建一个自定义函数,将标准化应用于每个组的数据:

let data = { 'A': [ 'foo', 'bar', 'foo', 'bar',
                    'foo', 'bar', 'foo', 'foo' ],
            'C': [ 1, 3, 2, 4, 5, 2, 6, 7 ],
            'D': [ 3, 2, 4, 1, 5, 6, 7, 8 ] };
let df = new DataFrame(data);
let group_df = df.groupby([ "A"]);

// (x - x.mean()) / x.std()
const norm = (x) => {
  return x.sub(x.mean()).div(x.std());
};

group_df.apply(norm).print();

在前面的代码中,我们首先按列A对数据进行分组。然后,我们创建一个名为norm的自定义函数,它包含应用于数据的标准化过程,给出以下输出:

Figure 7.21 – Standardizing grouped data

图 7.21–标准化分组数据

我们已经看到了如何使用.apply方法为groupby操作创建自定义函数。因此,我们可以根据需要,基于我们希望对数据执行的操作类型来创建自定义函数。

在下一节中,我们将研究数据聚合,以及如何为分组数据的不同列分配不同的数据聚合操作。

分组数据的数据聚合

数据聚合涉及收集数据并以汇总形式呈现的过程,例如显示其统计数据。聚合本身就是为统计目的收集数据并将其表示为数字的过程。在本节中,我们将了解如何在丹佛号中执行数据聚合

以下是所有可用聚合方法的列表:

  • mean():至计算分组数据的平均值
  • std():计算标准偏差
  • sum():获取一组数值的和
  • count():统计每组数值的总数
  • min():获取每组最小值
  • max():获取每组最大值

在本章的开头,我们看到了如何调用前面在组数据中列出的一些聚合方法。groupby类还包含一个名为.agg的方法,它允许我们同时对不同的列应用不同的聚合操作。

单列分组的数据聚合

我们将创建一个数据框,将数据框按列分组,然后在不同的列上应用两种不同的聚合方法:

let data = { 'A': [ 'foo', 'bar', 'foo', 'bar',
      'foo', 'bar', 'foo', 'foo' ],
    'C': [ 1, 3, 2, 4, 5, 2, 6, 7 ],
    'D': [ 3, 2, 4, 1, 5, 6, 7, 8 ] };
let df = new DataFrame(data);
let group_df = df.groupby([ "A"]);

group_df.agg({ C:"mean", D: "count" }).print();

我们创建了一个数据框,然后按列A对数据框进行分组。然后通过调用.agg方法聚合分组的数据。

.agg方法接收一个对象,该对象的键是数据框中列的名称,值是我们要应用于每个列的聚合方法。在前面的代码块中,我们指定了键为CD,值为meancount:

Figure 7.22 – Aggregation method on group data

图 7.22–分组数据的聚合方法

我们已经看到了如何在单列分组上进行数据聚合。现在让我们看看如何对按双列分组的数据帧执行相同的操作。

双柱分组的数据聚合

对于双列分组,我们应用相同的聚合方法:

let data = { 'A': [ 'foo', 'bar', 'foo', 'bar',
      'foo', 'bar', 'foo', 'foo' ],
    'B': [ 'one', 'one', 'two', 'three',
      'two', 'two', 'one', 'three' ],
    'C': [ 1, 3, 2, 4, 5, 2, 6, 7 ],
    'D': [ 3, 2, 4, 1, 5, 6, 7, 8 ] };
let df = new DataFrame(data);
let group_df = df.groupby([ "A", "B"]);

group_df.agg({ C:"mean", D: "count" }).print();

前面的代码给出了以下输出:

Figure 7.23 – Aggregation method on two-column grouped data

图 7.23–两列分组数据的聚合方法

在本节中,我们已经看到了如何使用.apply方法为分组数据创建自定义函数,以及如何对分组数据的每一列执行联合数据聚合。这里显示的示例可以扩展到任何特定的数据,并且可以根据需要创建自定义函数。

group by 在真实数据上的简单应用

我们已经看到了如何在虚拟数据上使用groupby方法。在本节中,我们将看到如何使用groupby来分析数据。

我们将利用这里流行的titanic数据集:https://web . Stanford . edu/class/archive/cs/cs109/cs109 . 1166/stuff/titanic . CSV。我们将看看如何根据性别和阶级来估计泰坦尼克号事故中幸存的平均人数。

让我们将titanic数据集读入一个数据框,并输出其中的一些行:

const dfd = require('danfojs-node')
async function analysis(){
  const df = dfd.read_csv("titanic.csv")
  df.head().print()
}
analysis()

前面的代码应该输出下表:

Figure 7.24 – DataFrame table

图 7.24–数据框架表

从数据集中,我们希望根据性别(T0)和旅行等级(T1)来估计存活下来的平均人数。

下面的代码显示了如何如前所述估计平均存活率:

const dfd = require('danfojs-node')
async function analysis(){
  const df = dfd.read_csv("titanic.csv")
  df.head().print()

  //groupby Sex column
  const sex_df = df.groupby(["Sex"]).col(["Survived"]).mean()
  sex_df.head().print()

  //groupby Pclass column
  const pclass_df = df.groupby(["Pclass"]).col(["Survived"]).mean()
  pclass_df.head().print()
}
analysis()

下表显示了每种性别的平均存活率:

Figure 7.25 – Average rate of survival based on sex

图 7.25-基于性别的平均存活率

下表显示了每个班级的平均存活率:

Figure 7.26 – Average rate of survival based on Pclass

图 7.26–基于 Pclass 的平均存活率

在本节中,我们看到了如何使用groupby操作来分析真实数据的简要介绍。

总结

本章我们广泛讨论了在 Danfo.js 中实现的groupby操作,我们讨论了分组数据,提到目前 Danfo.js 只支持单列和双列分组;有一个计划是在 Danfo.js 的未来版本中使这变得更加灵活。我们还展示了如何迭代分组数据并访问组键及其关联的分组数据。我们研究了如何在不循环的情况下获得与组合键相关联的分组数据。

我们还看到了.apply方法如何为我们的分组数据创建自定义数据聚合函数,最后,我们演示了如何同时对分组数据的不同列执行不同的聚合函数。

本章为我们提供了对数据进行分组的知识,更重要的是,它向我们介绍了 Danfo.js 的内部结构。通过这一点,我们可以将groupby方法重塑为我们想要的味道,并有能力为 Danfo.js 做出贡献。

在下一章中,我们将继续学习更多的应用基础知识,包括如何使用 Danfo.js 构建一个数据分析 web 应用,一个无代码环境。我们还将看到如何将 Danfo.js 方法转化为 React 组件。