数据分析(6)--Pandas+MultiIndex多层索引与分组

MultiIndex

MultiIndex,即具有多个层次的索引,有些类似于根据索引进行分组的形式。通过多层次索引,我们就可以使用高层次的索引,来操作整个索引组的数据。

创建方式

第一种

我们在创建Series或DataFrame时,可以通过给index(columns)参数传递多维数组,进而构建多维索引。【数组中每个维度对应位置的元素,组成每个索引值】
多维索引的也可以设置名称(names属性),属性的值为一维数组,元素的个数需要与索引的层数相同(每层索引都需要具有一个名称)。

第二种

我们可以通过MultiIndex类的相关方法,预先创建一个MultiIndex对象,然后作为Series与DataFrame中的index(或columns)参数值。同时,可以通过names参数指定多层索引的名称。

  • from_arrays:接收一个多维数组参数,高维指定高层索引,低维指定底层索引。
  • from_tuples:接收一个元组的列表,每个元组指定每个索引(高维索引,低维索引)。
  • from_product:接收一个可迭代对象的列表,根据多个可迭代对象元素的笛卡尔积进行创建索引。

from_product相对于前两个方法而言,实现相对简单,但是,也存在局限。

mport numpy as np
import pandas as pd

# 创建单层次的索引。
x = pd.Series([1, 2, 3], index=["a", "b", "c"])
# 创建多层次索引。index指定一个二维数组,每个层次是一个一维数组。
x = pd.Series([123, 2123, 13123], index=[["东部地区", "东部地区", "南部地区"], ["城市1", "城市2", "城市3"]])
# x.index

# x = pd.DataFrame(np.random.randint(10, 100, size=(4, 4)), 
#                  index=[["东部地区", "东部地区", "南部地区", "南部地区"], ["城市1", "城市2", "城市3", "城市4"]],
#                 columns=[["2017", "2017", "2018", "2018"], ["上半年", "下半年", "上半年", "下半年"]])
# display(x.index, x.columns)

# 也可以这样做。
# ind = pd.Index(["a", "b", "c"], name="索引的名字")
# x = pd.Series([1, 2, 3], index=ind)
# x.index

# 通过构造器创建MultiIndex对象。
# mi = pd.MultiIndex(levels=[['东部地区', '南部地区'], ['城市1', '城市2', '城市3']],
#            labels=[[0, 0, 1], [0, 1, 2]], names=["外层索引名称", "内层索引名称"])
# x = pd.Series([1, 2, 3], index=mi)
# 还可以通过MultiIndex类提供的类方法来创建对象。
# 传递一个二维列表(数组)来创建多层次索引。每个元素(一维数组来指定每层索引的标签内容。)
# mind = pd.MultiIndex.from_arrays([["东部地区", "东部地区", "南部地区"], ["城市1", "城市2", "城市3"]], names=["外层", "内层"])
# 参数为含有元组的列表,每个元素的元素个数与索引的层数数量相同。每个元组分别指定(外层, 内层)
# from_arrays可以看做是按列的方式指定索引。from_tuples可以看做是按行的方式来指定索引。
# mind = pd.MultiIndex.from_tuples([("东部地区", "城市1"), ("东部地区", "城市2"), ("南部地区", "城市3")])

# 通过笛卡尔积组合,创建多层次索引。
mind = pd.MultiIndex.from_product([["东部地区", "南部地区"], ["城市1", "城市2"]])
x = pd.Series([1, 2, 3, 4], index=mind)
x
# 使用笛卡尔积的方式,创建多层次索引,相对简单,但是也具有局限。

多层索引操作

对于多层索引,同样也支持单层索引的相关操作,例如,索引元素,切片,索引数组选择元素等。我们也可以根据多级索引,按层次逐级选择元素。
多层索引的优势:通过创建多层索引,我们就可以使用高层次的索引,来操作整个索引组的数据。
格式:

  • s[操作]
  • s.loc[操作]
  • s.iloc[操作]

其中,操作可以是索引,切片,数组索引,布尔索引。

Series多层索引

  • 通过loc(标签索引)操作,可以通过多层索引,获取该索引所对应的一组值。
  • 通过iloc(位置索引)操作,会获取对应位置的元素值(与是否多层索引无关)。
  • 通过s[操作]的行为有些诡异,建议不用。
  • 对于索引(单级),首先按照标签选择,如果标签不存在,则按照位置选择。
  • 对于多级索引,则按照标签进行选择。
  • 对于切片,如果提供的是整数,则按照位置选择,否则按照标签选择。
  • 对于数组索引, 如果数组元素都是整数,则根据位置进行索引,否则,根据标签进行索引(此时如果标签不存在,也不会出现错误)。
ind = pd.MultiIndex.from_product([["1东部地区", "2南部地区", "3西部地区", "4北部地区"], ["城市1", "城市2"]])
x = pd.Series([1, 2, 3, 4, 5, 6, 7, 8], index=mind)
display(x)
# 通过层次索引,我们可以获取属于该标签下的一组元素。
# x.loc["东部地区"]
# 我们也可以按照层次来逐级进行访问。[外层索引, 内层索引]
# x.loc["东部地区", "城市1"]

# 多层次索引也支持切片操作
# x.loc["1东部地区":"3西部地区"]

# 多层次索引的数组索引与布尔索引操作。
# x.loc[["1东部地区", "4北部地区"]]

# 当通过位置访问元素时,就是按照记录的位置进行访问的,与当前的Series(DataFrame)是否具有
# 多层次索引无关。
# x.iloc[2:5]
x.loc[[True, False, False, True]]

DataFrame多层索引

  • 通过loc(标签索引)操作,可以通过多层索引,获取该索引所对应的一组值。
  • 通过iloc(位置索引)操作,会获取对应位置的一行(与是否多层索引无关)。
  • 通过s[操作]的行为有些诡异,建议不用。
  • 对于索引,根据标签获取相应的列(如果是多层索引,则可以获得多列)。
  • 对于数组索引, 根据标签,获取相应的列(如果是多层索引,则可以获得多列)。
  • 对于切片,首先按照标签进行索引,然后再按照位置进行索引(取行)。
df = pd.DataFrame(np.random.randint(10, 100, size=(4, 4)), 
                 index=[["东部地区", "东部地区", "南部地区", "南部地区"], ["城市1", "城市2", "城市3", "城市4"]],
                columns=[["2017", "2017", "2018", "2018"], ["上半年", "下半年", "上半年", "下半年"]])
# df.loc["南部地区", "城市3"]
# df.iloc[0:2]
df

交换索引

我们可以调用DataFrame对象的swaplevel方法来交换两个层级索引。该方法默认对倒数第2层与倒数第1层进行交换。我们也可以指定交换的层。层次从0开始,由外向内递增(或者由上到下递增),也可以指定负值,负值表示倒数第n层。除此之外,我们也可以使用层次索引的名称来进行交换。

索引排序

我们可以使用sort_index方法对索引进行排序处理。

  • level:指定根据哪一层进行排序,默认为最外(上)层。该值可以是数值,索引名,或者是由二者构成的列表。
  • inplace:是否就地修改。默认为False。
df = pd.DataFrame(np.random.randint(1, 10, (5, 3)), index=[["b", "b", "b", "a", "a"], ["y", "y", "x", "x", "x"], ["u","k", "w", "w","u" ]])
df.index.names = ["level1", "level2", "level3"]
display(df)
# 交换索引的层级。索引的层级,由外向内,0,1,2这样进行递增。如果我们没有显式指定索引的层架,
# 则默认交换最内层与倒数第二层。索引的层级也可以为负值,从最内从到最外层,-1,-2,-3这样进行递增。
# df.swaplevel()   # 相当于df.swaplevel(-1, -2)
# 将最内层与最外层索引进行交换。
# df.swaplevel(0, -1)
# df.swaplevel(0, 2)
# 我们除了可以指定索引的层次之外,我们也可以通过索引的名字(name)来表示对应的索引。
# df.swaplevel("level1", "level3")

# 对索引进行排序。如果没有指定层次,则按最外层进行排序。
# df.sort_index(level="level3")
# df.sort_index(level=1
# 我们也可以提供列表,进行多层次的排列。
# df.sort_index(level=[1, "level3"])

索引堆叠

通过DataFrame对象的stack方法,可以进行索引堆叠,即将指定层级的列转换成行。
level:指定转换的层级,默认为-1。

取消堆叠

通过DataFrame对象的unstack方法,可以取消索引堆叠,即将指定层级的行转换成列。
level:指定转换的层级,默认为-1。
fill_value:指定填充值。默认为NaN。

df = pd.DataFrame(np.random.randint(1, 10, (5, 3)), index=[["b", "b", "b", "a", "a"], ["y", "y", "x", "x", "x"], ["u","k", "w", "w","u" ]]
                 ,columns=[["a", "a", "b"], ["c", "d", "d"]])
display(df)
# 进行索引堆叠。将列索引转换成行索引。
# df.stack()
# 我们可以通过level指定转换的层。默认为最里层(-1)。
# df.stack(0)

# 惊醒索引取消堆叠,将行索引转换成列索引。我们可以通过level来指定层级。(默认为-1。)
# 通过fill_value参数指定,如果产生空值,使用该参数的值进行填充。
df.unstack(1, fill_value=1000)

设置索引

在DataFrame中,如果我们需要将现有的某一(几)列作为索引列,可以调用set_index方法来实现。

  • drop:是否丢弃作为新索引的列,默认为True。
  • append:是否以追加的方式设置索引,默认为False。
  • inplace:是否就地修改,默认为False。

重置索引

调用在DataFrame对象的reset_index,可以重置索引。该操作与set_index正好相反。

  • level:重置索引的层级,默认重置所有层级的索引。如果重置所有索引,将会创建默认整数序列索引。
  • drop:是否丢弃重置的索引列,默认为False。
  • inplace:是否就地修改,默认为False。
df = pd.DataFrame(np.random.randint(0, 100, (5, 3)))
# 设置索引。drop参数指定,设置索引列后,该列是否从当前的DataFrame中删除。默认为True。
# df.set_index([0, 1], drop=False)

# append参数设置以前的索引列是否保留。(新的索引列是替换以前的索引,还是追加。),默认为False,替换的方式。
# df.set_index(2, append=True)

df = pd.DataFrame(np.random.randint(0, 100, (5, 3)), index=[["a", "a", "a", "b", "b"], ["c", "c", "d", "d", "d"]])
display(df)
# 重置索引,可以看成是设置索引的逆操作。
# level指定重置的层级。如果没有指定,则重置所有层级。
# df.reset_index(level=0)
# 可以通过drop参数来指定,重置索引后,该索引列是否丢弃。默认为False。
df.reset_index(level=0, drop=True)

分组与聚合

分组与聚合操作与数据库中的分组与聚合相似。

groupby分组

我们可以通过groupby方法来对Series或DataFrame对象实现分组操作。该方法会返回一个分组对象:

  • 对于Series分组,返回SeriesGroupBy对象。
  • 对于DataFrame分组,DataFrameGroupBy对象。

迭代

如果直接查看(输出)该对象,并不能看到任何的分组信息(这点不同于列表类型)。不过,我们还是有能力获取分组对象的数据信息的。分组对象是可迭代对象类型(Iterable),因此,我们可以采用如下的方式进行遍历:

  • 获取迭代器,进行迭代。迭代每次会返回一个元组,第1个元素为用来分组的key,第2个元素为该组对应的数据。
  • 使用for循环来对分组对象进行迭代。

说明:

  • 对于多层索引,迭代返回元组的第一个元素(分组的key),依然还是一个元组。

聚合

所谓聚合,就是执行多个值变成一个值的操作。而在Python中,就是将一个矢量(数组)变成标量。
在分组之后,我们就可以调用分组对象的mean,sum等聚合函数,对每个分组进行聚合。

说明:

  • 在聚合的结果集中,默认会使用分组的键作为索引,我们可以在groupby方法中通过as_index参数来控制。
  • 我们也可以在聚合之后调用reset_index方法来实现as_index=False的功能。

分组对象的属性与方法

  • groups(属性) 返回一个字典类型对象,包含分组信息。
  • size 返回每组记录的数量。
  • discribe 分组查看统计信息。
  • get_group 接受一个组名(key),返回该组对应的数据。

分组的方式

通过by参数进行分组

使用groupby进行分组时,通过参数by指定分组,参数by可以是如下的形式:

  • 字符串或字符串数组:指定分组的列名或索引名。(如果索引没有命名,可以使用pd.Grouper(level=1)来指定索引的层级)
  • 字典或Series:key指定索引,value指定分组依据,即value值相等的,会分为一组。
  • 数组(列表):会根据数组的值进行对位分组。长度需要与列(行)相同。
  • 函数:接受索引,返回分组依据的value值。

说明:

  • 使用groupby分组时,可以使用sort参数指定是否对分组的key进行排序,默认为True(指定False可以提高性能)。

通过level参数进行分组

我们也可以通过level指定索引的层级,groupby会将该层级索引值相同的数据进行分组。level可以是整数值,索引名。

df = pd.DataFrame({"a":[1, 2, 1, 2], "b":[5, 6, 7, 8], "c":[9, 10, 11, 1]})
display(df)
g = df.groupby("a")
# 分组对象不像列表对象那样,可以打印直接看到数据信息。
# 如果需要看到分组对象的数据信息,需要自行迭代查看。

# 迭代方式:
# 1 获得分组对象的迭代器,然后调用next获取元素。
# 2 使用for循环。
# g.__iter__()
# i = iter(g)
# i.__next__()
# display(next(i))
# display(next(i))

# 当对分组对象进行迭代时,每次会返回一个元组类型。元组中含有两个元素:组名(索引为0的与元素)与该组
# 对应的数据(索引为1的元素)
# for k, v in g:
#     display(k, v)

# display(df.sum(axis=1)) 
# 在分组对象上调用聚合方法。就可以针对每个组进行聚合操作。

# 分组聚合时,默认会使用分组键来充当索引,如果需要重置索引,可以在groupby中,将as_index参数
# 设置为False。(默认为True)
g = df.groupby("a", as_index=False)
# g.mean()
# 返回一个字典类型,字典类型中包含分组的信息。(key:组名【用来分组的值】, value:每组数据记录的索引。)
# g.groups
# 返回每组的记录数。
# display(g.size())
# 按组来统计每列的信息。
# display(g.describe())
# 根据组名返回该组对应的数据。
# display(g.get_group(1))
# display(g.get_group(2))
# groupby分组的几种形式。
# display(df)
# 1 指定一个索引。
# g = df.groupby("a")
# g.mean()
# g["b"].mean()
# 2 指定多个索引分组。(数组)
# g = df.groupby(["a", "b"])
# 3 根据Series或字典对象来进行分组。key指定索引,value映射为组的值。分组时,就会把value相同的值分到一组。
# g = df.groupby({0: "g1", 1:"g2", 2:"g1", 3: "g2"})
# 4 根据函数进行分组。函数需要具有一个参数,该参数用来接收索引,然后返回该索引对应的组。
# g = df.groupby(lambda x : x % 2)
# for k, v in g:
#     display(k, v)

# 根据索引层级进行分组。此时,会把该层级索引值相同的,划分为一组。
df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=[["a", "a", "b"], ["c", "d", "d"]])
display(df)
g = df.groupby(level=1)
for k, v in g:
    display(k, v)

结果样例

agg聚合

我们可以使用DataFrame或者分组对象的agg / aggregate方法实现多个聚合操作。
方法可以接受如下形式:

  • 字符串函数名
  • 函数对象
  • 列表 可提供多个函数(函数名),进行多种聚合。
  • 字典 可针对不同的列实现不同的聚合方式。

说明:

  • 对于分组对象,会使用函数名作为聚合之后的列名。如果需要自定义列名,可以提供元组的列表,格式为:
    [(列名1, 聚合函数1), (列名2, 聚合函数2), ……]
# agg聚合方法。
df = pd.DataFrame({"a":[1, 2, 1, 2], "b":[5, 6, 7, 8], "c":[9, 10, 11, 1]})
display(df)
# DataFrame对象与分组对象可以调用agg(aggregate)进行聚合操作。

# agg进行聚合的方式:
# 1 传递函数名字符串。
# df.agg("mean")
# df.mean()
# 2 传递一个列表。
# df.agg(["mean", "max", "min"])
# 3 传递一个字典,key指定列名(索引),value指定聚合方法。这样,
# 可以做到每个类进行不同的聚合方式。
# df.agg({"a":"mean", "b":"max", "c":"min"})
# df.agg({"a":["mean", "max"], "b":["max", "min"], "c":["min", "sum"]})
# 4 传递一个函数。该函数具有一个参数,参数用来接收每一列(行),然后返回聚合之后的结果。
# 通过函数,我们可以实现自定义的聚合。
# df.agg(lambda x: x.max() - x.min())

g = df.groupby("a")
# 默认情况下,会使用聚合方法名称来作为列名称,我们可以通过指定一个元素,
# (显示名称,聚合操作)来更改显示的列名称。
g.agg([("平均值","mean"), ("最大值", "max"), ("最小值", "min")])

结果样例

apply

对分组对象,可以调用apply函数,apply函数需要传入一个操作函数,该函数会依次接收每个分组的数据,并在函数体中进行处理,返回处理之后的结果。
最后,apply会将每个分组调用函数的返回结果合并(concat),作为最终的处理结果。

说明:

  • apply的函数的参数函数除了必要的一个参数(用来接收每个分组的数据)外,还可以定义额外的参数。其他参数可以在调用apply时,一并传入(置于参数函数的后面)。
  • 在apply分组中,传入每个组的数据,name属性为该分组键的值。
df = pd.DataFrame({"a":[1, 2, 1, 2], "b":[5, 6, 7, 8], "c":[9, 10, 11, 1]})
display(df)
g = df.groupby("a")
# 分组对象可以调用apply方法,对每个组执行指定的应用。
# 注意:在当前的实现中,分组对象的apply方法调用时,第一个组会执行两次。(传递两次)
# g.apply(lambda x: x.std())

# 函数的参数会接收每个组,我们在函数中处理组,并返回结果。
# 
display(g.apply(lambda x : x.describe()))
display(g.describe())

结果样例

部分数据来自光环笔记!

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值