标准化Pandas数据时加快循环速度

时间:2018-10-20 15:37:34

标签: python pandas numpy vectorization

我有一个熊猫数据框:

|  col1  | heading |
|--------|---------|
|heading1|   true  |
|abc     |  false  |
|efg     |  false  |
|hij     |  false  |
|heading2|   true  |
|klm     |  false  |
|...     |  false  |

此数据实际上是“顺序的”,我想将其转换为以下结构:

|  col1  |  Parent   |
|---------------------
|heading1|  heading1 |
|abc     |  heading1 | 
|efg     |  heading1 |
|hij     |  heading1 |
|heading2|  heading2 |
|klm     |  heading2 |
|...     |  headingN |

我有+ 10M行,因此此方法花费的时间太长:

df['Parent'] = df['col1']

for index, row in df.iterrows():
    if row['heading']:
        current = row['col1']
    else:
        row.loc[index, 'Parent'] = current

您对更快的流程有什么建议吗?

5 个答案:

答案 0 :(得分:5)

您可以将maskffill一起使用:

df.assign(heading=df.col1.mask(~df.col1.str.startswith('heading')).ffill())

       col1   heading
0  heading1  heading1
1       abc  heading1
2       efg  heading1
3       hij  heading1
4  heading2  heading2
5       klm  heading2

这可以通过将所有不以heading开头的值替换为NaN的方式起作用,然后填充最后一个非nan值:

df.col1.mask(~df.col1.str.startswith('heading'))

0    heading1
1         NaN
2         NaN
3         NaN
4    heading2
5         NaN
Name: col1, dtype: object

df.col1.mask(~df.col1.str.startswith('heading')).ffill()

0    heading1
1    heading1
2    heading1
3    heading1
4    heading2
5    heading2
Name: col1, dtype: object

答案 1 :(得分:3)

可能不是一个非常常用的解决方案,但是您可以cumsum逻辑列并使用它来获取每一行的相应标题。本质上,我们定义了一个分段常数索引数组,该数组只会为原始True列上的每个heading值递增。

import pandas as pd

# set up some dummy data
df = pd.DataFrame({'heading': [True, False, False, False, True, False, False]},
                  index=['heading1', 'foo', 'bar', 'baz', 'heading2', 'quux', 'quuz'])

# get every 'heading' index
headings = df.index[df.heading]
# fetch which row corresponds to which 'heading'
indices = df.heading.cumsum() - 1
# fetch the actual headings for each row
df['parent'] = headings[indices]

print(df)

以上代码的输出为

          heading    parent
heading1     True  heading1
foo         False  heading1
bar         False  heading1
baz         False  heading1
heading2     True  heading2
quux        False  heading2
quuz        False  heading2

您可以从中drop不必要的heading列中。当然,您可以直接获取您拥有的逻辑数组并对其进行处理:

headline = df.index.str.startswith('heading') # bool Series
headings = df.index[headline]
indices = df.heading.cumsum() - 1
df['parent'] = headings[indices]

答案 2 :(得分:2)

我也想到了填补。通过使用df.pop(),我们可以确保该列也消失了。

df['Parent'] = df['col1'].mul(df.pop('heading')).replace('',np.nan).ffill()

完整示例

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'col1': ['heading1', 'abc', 'efg', 'hij', 'heading2', 'klm'],
    'heading': [True, False, False, False, True, False]
})

df['Parent'] = df['col1'].mul(df.pop('heading')).replace('',np.nan).ffill()
print(df)

返回:

       col1    Parent
0  heading1  heading1
1       abc  heading1
2       efg  heading1
3       hij  heading1
4  heading2  heading2
5       klm  heading2

答案 3 :(得分:1)

where + pop + ffill

您可能会发现这更有效。来自@AntonvBR的数据。

df['Parent'] = df['col1'].where(df.pop('heading')).ffill()

print(df)

       col1    Parent
0  heading1  heading1
1       abc  heading1
2       efg  heading1
3       hij  heading1
4  heading2  heading2
5       klm  heading2

答案 4 :(得分:1)

要使用完全不同的方法,可以通过遍历布尔数组并随后将其用作标头的映射来获取索引。我不知道标题映射的速度有多快,但是您可以快速为布尔值建立索引。

import numpy as np
from numba import jit
bool_array = np.array([True, False], dtype=np.bool)
boolean_array = np.random.choice(bool_array, size=100000000)
@jit(nopython=True)
def reassign(boolean_array):
    b = np.zeros(shape=(len(boolean_array),), dtype=np.int32)
    b[0] = 0
    for i in range(1,len(boolean_array)):
        if boolean_array[i]:
            b[i] = i
        else:
            b[i] = b[i-1]
    return b

import time
start = time.time()
print(reassign(boolean_array))
print("took {} seconds".format(time.time()-start))
  

使用Numba拍摄0.5秒,不使用Numba拍摄130秒,持续100毫米