Перебор строк в пандах для проверки состояния - PullRequest
0 голосов
/ 27 августа 2018

У меня есть следующий DF в пандах.

+-------+-------+
| Col_A | Col_B |
+-------+-------+
|  1234 |       |
|  6267 |       |
|  6364 |       |
|   573 |       |
|     0 |       |
|   838 |       |
|    92 |       |
|  3221 |       |
+-------+-------+

Col_B должен быть заполнен значениями True или False. По умолчанию это False, но когда первый 0 был «увиден», остальная часть DF должна быть True. DF имеет более 100 000 строк.

Каким будет самый быстрый способ установить значения в col_B равными «True», поскольку появляется первое значение «0» в Col_A?

+-------+--------+
| Col_A | Col_B  |
+-------+--------+
|  1234 | False  |
|  6267 | False  |
|  6364 | False  |
|   573 | False  |
|     0 | True   |
|   838 | True   |
|    92 | True   |
|  3221 | True   |
+-------+--------+

Ответы [ 6 ]

0 голосов
/ 27 августа 2018

Многие методы были представлены здесь, и я не мог устоять. Мне пришлось провести небольшое сравнение производительности между этими:

%timeit vivek_kumar()
16.6 ms ± 495 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit numbered_user()
6.69 ms ± 116 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit warpri()
14 ms ± 216 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit jpp()
2.21 ms ± 96.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit wen()
991 µs ± 20.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit pirsquared()
938 µs ± 24.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Это сравнение было выполнено на фрейме данных длиной 80 тыс. Строк для проверки масштабируемости. Казалось бы, решения wen и piRsquared являются наиболее эффективными. Не стесняйтесь задавать эти ответы.

РЕДАКТИРОВАТЬ: для прозрачности здесь приведены функции, используемые для выполнения теста:

def vivek_kumar():
    data = df.copy()
    first_index = data.loc[data['Col_A'] == 0, 'Col_A'].index[0]
    data.loc[:first_index, 'Col_B'] = False
    data.loc[first_index:, 'Col_B'] = True

def numbered_user():
    data = df.copy()
    idx = data.Col_A.eq(0).idxmax()
    data['Col_B'] = False
    data.loc[idx:, 'Col_B'] = True

def warpri():
    data = df.copy()
    def update_col_b(col_a):
        return col_a == 0
    data['Col_B'] = data.Col_A.apply(update_col_b)

def jpp():
    data = df.copy()
    idx = next((i for i, j in enumerate(data['Col_A']) if j == 0), len(data['Col_A']))
    data['Col_B'] = ~(data.index < idx)

def wen():
    data = df.copy()
    data['Col_B'] = data.Col_A.eq(0).cummax()

def pirsquared():
    data = df.copy()
    # This would return a copy.  My preferred approach
    # return data.assign(Col_B=np.logical_or.accumulate(data.Col_A.values == 0))
    # This edits the dataframe in place but properly compares against the other proposals
    df['Col_B'] = np.logical_or.accumulate(data.Col_A.values == 0)

EDIT2: Следуя указаниям piRSquared, здесь также проводится сравнение между использованием assign для создания копии кадра данных и использованием = для изменения существующего кадра данных:

def pirsquared1():
    data = df.copy()
    data = data.assign(Col_B=np.logical_or.accumulate(data.Col_A.values == 0))

%timeit pirsquared1()
923 µs ± 32.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

def pirsquared2():
    data = df.copy()
    df['Col_B'] = np.logical_or.accumulate(data.Col_A.values == 0)

%timeit pirsquared2()
598 µs ± 35.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
0 голосов
/ 27 августа 2018

Вы можете использовать Numpy's accumulate метод ufunc logical_or

df.assign(Col_B=np.logical_or.accumulate(df.Col_A.values == 0))

   Col_A  Col_B
0   1234  False
1   6267  False
2   6364  False
3    573  False
4      0   True
5    838   True
6     92   True
7   3221   True
0 голосов
/ 27 августа 2018

Использование eq с cummax

df.A.eq(0).cummax()
Out[5]: 
0    False
1    False
2    False
3    False
4     True
5     True
6     True
7     True
Name: A, dtype: bool
0 голосов
/ 27 августа 2018

Вы можете использовать next с выражением генератора. Это будет более эффективно в случае большого ряда, где 0 появляется в начале.

@ user3483203's Решение на основе NumPy должно подойти для общего использования.

df = pd.DataFrame({'A': [1234, 6267, 6364, 573, 0, 838, 92, 3221]})

idx = next((i for i, j in enumerate(df['A']) if j == 0), len(df['A']))

df['B'] = ~(df.index < idx)

# more verbose alternative:
# df['B'] = np.where(df.index < idx, False, True)

print(df)

      A      B
0  1234  False
1  6267  False
2  6364  False
3   573  False
4     0   True
5   838   True
6    92   True
7  3221   True
0 голосов
/ 27 августа 2018

Найти индекс первых 0 в col_A

first_index = df['col_A'][df['col_A'] == 0].index[0] - 1  #-1 to get index before 0

Рекомендуемый способ (спасибо @jpp):

first_index = df.loc[df['col_A'] == 0, 'col_A'].index[0] - 1

Затем используйте его, чтобы заполнить другой столбец:

df.loc[:first_index, 'col_B'] = False
df.loc[first_index:, 'col_B'] = True
0 голосов
/ 27 августа 2018

Использование idxmax с loc для назначения

idx = df.Col_A.eq(0).idxmax()
df['Col_B'] = False
df.loc[idx:, 'Col_B'] = True

   Col_A  Col_B
0   1234  False
1   6267  False
2   6364  False
3    573  False
4      0   True
5    838   True
6     92   True
7   3221   True

Использование assign:

Этот подход позволяет избежать изменения исходного DataFrame.

df.assign(Col_B=(df.index >= idx))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...