能否在赋值时进行数组操作?

6
我仔细查看了Stack Overflow,但没有找到有用的结果。目前我甚至不确定这是否可能,但因为我只是一个初学者,所以我想至少在这里问一下。
基本上,我有多个数据集,每个数据集约有800万行,我不想循环每一行。我在多个地方读到过向量化几乎总是pandas DataFrame中最快的操作,但我无法想出一种不需要循环的编写脚本的方法。速度至关重要,因为我不想让我的电脑连续运行一个月。
我必须从一个DataFrame中取两个值,并将它们用作另一个DataFrame的索引,并将该值更改为1。假设以下代码:
>>> import pandas as pd
>>> df1 = pd.DataFrame([[1,2],[3,4],[5,6]])
>>> df1.columns = ['A','B']
>>> df1
   A  B
0  1  2
1  3  4
2  5  6
>>> df2 = pd.DataFrame(0, index = list(df1['B']), columns = list(df1['A']))
>>> df2
   1  3  5
2  0  0  0
4  0  0  0
6  0  0  0

目前,我有一个像这样工作的for循环:

>>> listA = list(df1['A'])
>>> listB = list(df2['B'])
>>> row_count = len(listB)
>>> for index in range(row_count):
...     col = listA[index]
...     row = listB[index]
...     df2[col][row] = 1

使用for循环遍历range()迭代器似乎比iterrows()要快得多。但我希望让我的脚本尽可能地运行得更快(因为我有大量的数据),所以我想知道是否可以摆脱循环。我认为pandas模块有一种方法可以非常高效地完成这项工作,但我不知道是什么方法。
感谢任何帮助。
编辑:可能的重复问题并没有解决我的问题,因为我的目标不是将对角线值改为1;这只是一个巧合,因为我的示例非常简单。另外,如果我的编辑格式不正确,我很抱歉,我是新来社区的。

你可以直接使用 df2.loc[:, :] = 1,这会将数据框中的所有值都设置为 1。 - sirfz
@sirfz仅希望将df1中定义的索引对设置为1,而不是所有索引对。 - Andras Deak -- Слава Україні
我已更新我的答案,这样你就可以有一个标志或计数。 - piRSquared
3个回答

5

我认为你需要使用pd.get_dummies函数,但首先需要使用set_index函数将列B设置为索引:

print (df1.set_index('B')['A']) 
B
2    1
4    3
6    5
Name: A, dtype: int64

print (pd.get_dummies(df1.set_index('B')['A']))
   1  3  5
B         
2  1  0  0
4  0  1  0
6  0  0  1

如果有重复值,需要使用groupby和聚合函数max:
df1 = pd.DataFrame([[1,2],[3,4],[5,6], [1,6]])
df1.columns = ['A','B']
print (df1)
   A  B
0  1  2
1  3  4
2  5  6
3  1  6

df2 = pd.get_dummies(df1.set_index('B')['A'])
df2 = df2.groupby(level=0).max()
print (df2)
   1  3  5
B         
2  1  0  0
4  0  1  0
6  1  0  1

另一种由DYZ编辑的替代方法(重置索引并使用列进行引用):

print(pd.get_dummies(df1.set_index('B')['A']).reset_index().groupb‌​y('B').max())

无法处理 df1 = pd.DataFrame([[1,2],[3,4],[5,6],[1,6]])。但是这个可以:pd.get_dummies(df1.set_index('B')['A']).reset_index().groupby('B').sum() - DYZ
@DYZ - 谢谢,这是另一种解决方案。或者也许 pd.get_dummies(df1.set_index('B')['A']).groupb‌​y(level=0).sum() 也可以。 - jezrael
@DYZ - 我不确定 sum 是否合适,因为如果双倍的 1 需要输出 1,而不是 2。所以如果只需要输出 10,则需要使用 max。如果需要对重复项求和,则需要使用 sum - jezrael
我同意,max() 更好。 - DYZ
添加了第六个选项,非常快速。 - piRSquared
非常感谢您的回复。我已经成功地运行了我的脚本,仅用了7个小时,这比预期的1个月运行时间要好得多。我使用了每个人的解决方案(问题过于简单化,因为当时我的任务有点太复杂了无法解释),但我选择了@piRSquared的答案,因为他提供了最快的运行代码片段。 - spicypumpkin

3

numpy支持这种类型的索引/赋值。据我所知,pandas没有这个能力。

假设这是您的DataFrame:

df = pd.DataFrame(np.zeros((5, 5)), index=list('abcde'), columns=list('ABCDE'))

df
Out: 
     A    B    C    D    E
a  0.0  0.0  0.0  0.0  0.0
b  0.0  0.0  0.0  0.0  0.0
c  0.0  0.0  0.0  0.0  0.0
d  0.0  0.0  0.0  0.0  0.0
e  0.0  0.0  0.0  0.0  0.0

这个有索引:

df1 = pd.DataFrame({'C1': ['a', 'c', 'a', 'd', 'e', 'b', 'd'], 
                    'C2': ['B', 'D', 'A', 'E', 'A', 'A', 'E']})

df1
Out: 
  C1 C2
0  a  B
1  c  D
2  a  A
3  d  E
4  e  A
5  b  A
6  d  E

你可以通过以下方式删除重复的索引对:
df1 = df1.drop_duplicates()

现在,numpy支持arr[df1.C1, df1.C2]类型的索引,但它需要整数索引-而不是标签。您可以使用index.get_loc来实现这一点;它非常快速。

row_indexers = [df.index.get_loc(r_label) for r_label in df1.C1]
col_indexers = [df.columns.get_loc(c_label) for c_label in df1.C2]

如果您通过DataFrame的值属性访问底层的numpy数组,您可以执行以下操作:

df.values[row_indexers, col_indexers] = 1

df
Out: 
     A    B    C    D    E
a  1.0  1.0  0.0  0.0  0.0
b  1.0  0.0  0.0  0.0  0.0
c  0.0  0.0  0.0  1.0  0.0
d  0.0  0.0  0.0  0.0  1.0
e  1.0  0.0  0.0  0.0  0.0

问题是关于如何使用数组进行赋值。因此,我假设df2已经存在并且看起来像这样:
df1 = pd.DataFrame([[1,2], [3,4], [5,6], [1,6]], columns=list('AB'))
rows = df1['B'].unique()
cols = df1['A'].unique()
df2 = pd.DataFrame(0.0, index=rows, columns=cols)

df2
Out: 
     1    3    5
2  0.0  0.0  0.0
4  0.0  0.0  0.0
6  0.0  0.0  0.0

现在,如果您采用我的解决方案,结果将会是相同的:
row_indexers = [df2.index.get_loc(r_label) for r_label in df1.B]
col_indexers = [df2.columns.get_loc(c_label) for c_label in df1.A]


df2.values[row_indexers, col_indexers] = 1

df2
Out: 
     1    3    5
2  1.0  0.0  0.0
4  0.0  1.0  0.0
6  1.0  0.0  1.0

再次说明,这是一种假定您已经有 df2 并且想要进行赋值操作的解决方案。 get_dummies 或者 groupby 只会计算索引对并为您提供二进制矩阵。如果您的主要目标是重塑数据,那可能更有意义。但是当您说赋值时,我理解得更广泛(例如 df2.values[row_indexers, col_indexers] += 3)。


谢谢您的回答。但是这个例子如何考虑重复项?我试图使用jezrael提供的df1 = pd.DataFrame([[1,2],[3,4],[5,6], [1,6]])对您和jezrael的回答进行测试,但脚本一直报错:AssertionError: Number of manager items must equal union of block items # manager items: 3, # tot_items: 0 - spicypumpkin
这个例子(df1)据我所见没有重复项?你能展示一下你是如何执行的吗?假设它有重复项并且你想忽略它们,你可以直接使用 df1 = df1.drop_duplicates() - ayhan
1
我借鉴了你的逻辑和从Divakar那里学到的东西,得出了一个非常快速的答案。我必须记住这个以备将来使用。 - piRSquared
非常感谢您的回复。我已经成功地运行了我的脚本,仅用了7个小时,这比预期的1个月运行时间要好得多。我使用了每个人的解决方案(问题在当时过于简单化,因为我的任务有点太复杂了),但我选择了@piRSquared的答案,因为他提供了最快的运行代码片段。 - spicypumpkin
@Posh_Pumpkin 听到这个消息真是太好了。纯NumPy几乎总是更快。 - ayhan
显示剩余8条评论

3

答案
选项#6是我最好的尝试。

编辑:
对于选项6,您可以进行增量而不是覆盖作业。这个小修改应该可以让您计数。

df2.values[row_indexers, col_indexers] += 1
df1 = pd.DataFrame([[1,2], [3,4], [5,6], [1,6]], columns=['A', 'B'])
df2 = pd.DataFrame(0, index = list(df1['B'].unique()),
                    columns = list(df1['A'].unique()))

df1.groupby(list('AB')).size().gt(0).mul(1) \
    .reindex(df2.unstack().index, fill_value=0) \
    .unstack(0)

enter image description here


option 2

df1 = pd.DataFrame([[1,2], [3,4], [5,6], [1,6]], columns=['A', 'B'])
df2 = pd.DataFrame(0, index = list(df1['B'].unique()),
                    columns = list(df1['A'].unique()))

mux = pd.MultiIndex.from_arrays(df1.values.T).drop_duplicates()
df2.update(pd.Series(1, mux).unstack(0))
df2

在这里输入图片描述


选项 3

df1 = pd.DataFrame([[1,2], [3,4], [5,6], [1,6]], columns=['A', 'B'])
df2 = pd.DataFrame(0, index = list(df1['B'].unique()),
                    columns = list(df1['A'].unique()))

mux = pd.MultiIndex.from_arrays(df1.values.T).drop_duplicates()
df2.where(pd.Series(False, mux).unstack(0, fill_value=True), 1)

enter image description here


option 4

df1 = pd.DataFrame([[1,2], [3,4], [5,6], [1,6]], columns=['A', 'B'])
df2 = pd.DataFrame(0, index = list(df1['B'].unique()),
                    columns = list(df1['A'].unique()))

mux = pd.MultiIndex.from_arrays(df1.values.T).drop_duplicates()
df2[pd.Series(True, mux).unstack(0, fill_value=False)] = 1
df2

enter image description here


option 5

df1 = pd.DataFrame([[1,2], [3,4], [5,6], [1,6]], columns=['A', 'B'])
df2 = pd.DataFrame(0, index = list(df1['B'].unique()),
                    columns = list(df1['A'].unique()))

for i, (a, b) in df1.iterrows():
    df2.set_value(b, a, 1)
df2

在此输入图片描述

选项6
灵感来自@ayhan和@Divakar

df1 = pd.DataFrame([[1,2], [3,4], [5,6], [1,6]], columns=['A', 'B'])
df2 = pd.DataFrame(0, index = list(df1['B'].unique()),
                    columns = list(df1['A'].unique()))

row_indexers = df2.index.values.searchsorted(df1.B.values)
col_indexers = df2.columns.values.searchsorted(df1.A.values)

df2.values[row_indexers, col_indexers] = 1
df2

在此输入图片描述


时间
给定样本
代码:

df1 = pd.DataFrame([[1,2], [3,4], [5,6], [1,6]], columns=['A', 'B'])
df2 = pd.DataFrame(0, index = list(df1['B'].unique()),
                    columns = list(df1['A'].unique()))

def pir1():
    return df1.groupby(list('AB')).size().gt(0).mul(1) \
        .reindex(df2.unstack().index, fill_value=0) \
        .unstack(0)

def pir2():
    mux = pd.MultiIndex.from_arrays(df1.values.T).drop_duplicates()
    df2.update(pd.Series(1, mux).unstack(0))

def pir3():
    mux = pd.MultiIndex.from_arrays(df1.values.T).drop_duplicates()
    return df2.where(pd.Series(False, mux).unstack(0, fill_value=True), 1)

def pir4():
    mux = pd.MultiIndex.from_arrays(df1.values.T).drop_duplicates()
    df2[pd.Series(True, mux).unstack(0, fill_value=False)] = 1

def pir5():
    for i, (a, b) in df1.iterrows():
        df2.set_value(b, a, 1)

def pir6():
    row_indexers = df2.index.values.searchsorted(df1.B.values)
    col_indexers = df2.columns.values.searchsorted(df1.A.values)

    df2.values[row_indexers, col_indexers] = 1
    return df2

def ayhan1():
    row_indexers = [df2.index.get_loc(r_label) for r_label in df1.B]
    col_indexers = [df2.columns.get_loc(c_label) for c_label in df1.A]

    df2.values[row_indexers, col_indexers] = 1

def jez1():
    return pd.get_dummies(df1.set_index('B')['A']).groupby(level=0).max()

在此输入图片描述

更大的样本
代码:

from itertools import combinations
from string import ascii_letters
letter_pairs = [t[0] + t[1] for t in combinations(ascii_letters, 2)]
df1 = pd.DataFrame(dict(A=np.random.randint(0, 100, 10000),
                        B=np.random.choice(letter_pairs, 10000)))
df2 = pd.DataFrame(0, index = list(df1['B'].unique()),
                    columns = list(df1['A'].unique()))

enter image description here


你使用过以下代码吗?df1 = pd.DataFrame([[1,2],[3,4],[5,6], [1,6]]) df1.columns = ['A','B'] df2 = pd.DataFrame(0, index = list(df1['B']), columns = list(df1['A'])) - jezrael
我认为需要 df2 = pd.DataFrame(0, index = list(df1['B'].unique()), columns = list(df1['A'].unique())) - jezrael
然后 timings 就会很棒了 ;) - jezrael
被选为最快运行代码片段的候选者。谢谢! - spicypumpkin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接