Ускорение цикла при нормализации данных Pandas - PullRequest
0 голосов
/ 20 октября 2018

У меня есть pandas dataframe:

|  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 голосов
/ 20 октября 2018

Чтобы добавить совершенно другой метод, вы можете получить индекс, перебирая свой логический массив и затем используя его в качестве карты для ваших заголовков.Я не знаю, насколько быстро отображается заголовок, но вы можете быстро индексировать логические значения ..

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))

Занимает 0,5 секунды с Numba и 130 секунд без, для 100mm

0 голосов
/ 20 октября 2018

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
0 голосов
/ 20 октября 2018

Вы можете использовать mask с ffill:

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, а затем заполняет последнее значение non-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
0 голосов
/ 20 октября 2018

Я тоже думал о ffill.Используя 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
0 голосов
/ 20 октября 2018

Возможно, это не очень идиоматическое решение для панд, но вы можете 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]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...