首页 > 解决方案 > 为什么 Pandas 允许非唯一索引?

问题描述

Pandas 允许唯一和非唯一索引。一些操作只允许唯一索引。在什么情况下使用非唯一索引有意义?我认为强制索引的唯一性可以帮助提前发现数据完整性问题。

标签: pythonpandas

解决方案


免责声明:独特RangeIndex的永远是性能最好的选择。这个问题似乎倾向于使用唯一索引,并且专门寻找需要允许非唯一索引的情况。出于这个原因,从现在开始,将不讨论唯一索引,也不会讨论性能,只讨论非唯一索引的有用好处。

每当我们需要跟踪数据的原始来源时,一般位置非唯一索引是可取的。在很多情况下,在中间阶段,我们需要知道数据在哪一。这让我们可以计算如果索引是唯一的,这些信息可能会丢失,或者需要添加一个额外的列来跟踪它。下面只是几个例子:


交错多个数据帧:

考虑以下 2 个 DataFrame,假设每个 DataFrame 代表一天的数据。我们想按样本数而不是按天查看此每日数据:

df1 = pd.DataFrame([['10:05', 'Day 1', 'Sample 1'],
                    ['11:14', 'Day 1', 'Sample 2']])
df2 = pd.DataFrame([['10:03', 'Day 2', 'Sample 1'],
                    ['11:12', 'Day 1', 'Sample 2']])
# df1
       0      1         2
0  10:05  Day 1  Sample 1
1  11:14  Day 1  Sample 2

#df2
       0      1         2
0  10:03  Day 2  Sample 1
1  11:12  Day 1  Sample 2

因为 pandas 允许非唯一索引,所以我们concat可以sort_index

pd.concat([df1, df2]).sort_index()
       0      1         2
0  10:05  Day 1  Sample 1
0  10:03  Day 2  Sample 1
1  11:14  Day 1  Sample 2
1  11:12  Day 1  Sample 2

请注意,这是按行索引交错两个 DataFrame 的最快方法。另请注意,按列排序是不可行的12因为单词Day 1 Sample 1 将按字典顺序排序,这将遇到诸如 之类的值的问题Day 10,或者需要大量额外的计算才能正确处理数值。

我们可以添加ignore_index=Truesort_index,但这只会隐藏用新的范围索引覆盖,并且仍然依赖于concat返回具有非唯一索引的 DataFrame 的事实。

pd.concat([df1, df2]).sort_index(ignore_index=True)
       0      1         2
0  10:05  Day 1  Sample 1
1  10:03  Day 2  Sample 1
2  11:14  Day 1  Sample 2
3  11:12  Day 1  Sample 2

爆炸和减少

explode,特别是在 Series 上,是一种常见的操作,并且不丢失索引(允许重复)使得扩展和减少类型操作变得更加容易。

目标是从列中的逗号分隔字符串中删除任何重复值:

df = pd.DataFrame({
    'corresponding_id': [10, 20, 30],
    'col': ['a,b,c,a', 'b,c,c,b', 'a,a,a,a']
})

df

   corresponding_id      col
0                10  a,b,c,a
1                20  b,c,c,b
2                30  a,a,a,a

一个常见的解决方案可能类似于:

df['col'] = (
    df['col'].str.split(',').explode()
        .groupby(level=0).apply(lambda s: ','.join(np.unique(s)))
)

df

   corresponding_id    col
0                10  a,b,c
1                20    b,c
2                30      a

爆炸后的结果如下:

df['col'].str.split(',').explode()

0    a
0    b
0    c
0    a
1    b
1    c
1    c
1    b
2    a
2    a
2    a
2    a
Name: col, dtype: object

groupby因为我们可以相对于(索引)有重复level=0的索引,所以这只有在索引被保留时才有可能。如果索引不允许重复,我们将拥有:

0     a
1     b
2     c
3     a
4     b
5     c
6     c
7     b
8     a
9     a
10    a
11    a
Name: col, dtype: object

无法轻松确定值来自哪些行,因此更难以将它们放回原处。


扩展 DataFrame

使用重复标签从 DataFrame 中进行选择的能力对于扩展 DataFrame 非常有帮助。

df = pd.DataFrame({
    'Count': [2, 4],
    'Value': [1, 6]
})

有时我们需要扩展 DataFrame,在这些情况下,我们使用loc从 DataFrame 中进行选择:

df.loc[[0, 0, 1, 1, 1, 1], :]

注意结果是:

   Count  Value
0      2      1
0      2      1
1      4      6
1      4      6
1      4      6
1      4      6

我们能够根据重复标签从 DataFrame 中多次选择同一行(并且结果索引是非唯一的)。这很常见,以至于有一种方法Index.repeat可以根据列动态执行此操作:

df.loc[df.index.repeat(df['Count']), :]

   Count  Value
0      2      1
0      2      1
1      4      6
1      4      6
1      4      6
1      4      6

推荐阅读