Поместите значения строк слева до тех пор, пока в python не будет null

Я должен обрабатывать огромное количество данных. Каждая строка начинается с 1 или 0. Мне нужна фреймворк данных, где каждая строка начинается с 1, поэтому мне нужно повернуть все значения строк до первого значения 1.

Например:

0 1 0 0 1 0 0
1 0 0 0 0 1 1
0 0 0 1 0 0 1
0 0 0 0 0 1 1

Результат должен быть следующим:

1 0 0 1 0 0 0
1 0 0 0 0 1 1
1 0 0 1 0 0 0
1 1 0 0 0 0 0

Я не хочу использовать, пока, и т. Д., Потому что мне нужны более быстрые методы с помощью pandas или numpy.

У вас есть идея по этой проблеме?

Всего 4 ответа


Вы можете использовать cummax для маскировки всего положения, которое необходимо сдвинуть как NaN и sorted

df[df.cummax(1).ne(0)].apply(lambda x : sorted(x,key=pd.isnull),1).fillna(0).astype(int)
Out[310]: 
   1  2  3  4  5  6  7
0  1  0  0  1  0  0  0
1  1  0  0  0  0  1  1
2  1  0  0  1  0  0  0
3  1  1  0  0  0  0  0

Или мы используем функцию, оправдывающую запись Divakar (намного быстрее, чем применяемая сортировка)

pd.DataFrame(justify(df[df.cummax(1).ne(0)].values, invalid_val=np.nan, axis=1, side='left')).fillna(0).astype(int)
Out[314]: 
   0  1  2  3  4  5  6
0  1  0  0  1  0  0  0
1  1  0  0  0  0  1  1
2  1  0  0  1  0  0  0
3  1  1  0  0  0  0  0

Вы можете использовать numpy.ogrid здесь:

a = df.values
s = a.argmax(1) * - 1
m, n = a.shape
r, c = np.ogrid[:m, :n]
s[s < 0] += n
c = c - s[:, None]
a[r, c]

array([[1, 0, 0, 1, 0, 0, 0],
       [1, 0, 0, 0, 0, 1, 1],
       [1, 0, 0, 1, 0, 0, 0],
       [1, 1, 0, 0, 0, 0, 0]], dtype=int64)

Задержки

In [35]: df = pd.DataFrame(np.random.randint(0, 2, (1000, 1000)))

In [36]: %timeit pd.DataFrame(justify(df[df.cummax(1).ne(0)].values, invalid_val=np.nan, axis=1, side='left')).fillna(0).a
    ...: stype(int)
116 ms ± 640 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [37]: %%timeit
    ...: a = df.values
    ...: s = a.argmax(1) * - 1
    ...: m, n = a.shape
    ...: r, c = np.ogrid[:m, :n]
    ...: s[s < 0] += n
    ...: c = c - s[:, None]
    ...: pd.DataFrame(a[r, c])
    ...:
    ...:
11.3 ms ± 18.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Для производительности вы можете использовать numba . Элементарный цикл, но эффективная данная JIT-компиляция и использование более основных объектов на уровне C:

from numba import njit

@njit
def shifter(A):
    res = np.zeros(A.shape)
    for i in range(res.shape[0]):
        start, end = 0, 0
        for j in range(res.shape[1]):
            if A[i, j] != 0:
                start = j
                break
        res[i, :res.shape[1]-start] = A[i, start:]
    return res

Сравнительный анализ производительности

def jpp(df):
    return pd.DataFrame(shifter(df.values).astype(int))

def user348(df):
    a = df.values
    s = a.argmax(1) * - 1
    m, n = a.shape
    r, c = np.ogrid[:m, :n]
    s[s < 0] += n
    c = c - s[:, None]
    return pd.DataFrame(a[r, c])    

np.random.seed(0)
df = pd.DataFrame(np.random.randint(0, 2, (1000, 1000)))

assert np.array_equal(jpp(df).values, user348(df).values)

%timeit jpp(df)      # 9.2 ms per loop
%timeit user348(df)  # 18.5 ms per loop

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

def pp(x):
    n, m = x.shape
    am = x.argmax(-1)
    mam = am.max()
    xx = np.empty((n, m + mam), x.dtype)
    xx[:, :m] = x
    xx[:, m:] = 0
    xx = np.lib.stride_tricks.as_strided(xx, (n, mam+1, m), (*xx.strides, xx.strides[-1]))
    return xx[np.arange(x.shape[0]), am]

Он вводит ввод с требуемым количеством нулей, а затем создает скользящее окно с использованием as_strided . Это устраняется с помощью причудливой индексации, но не обязательно, что последнее измерение не индексируется. Копирование строк оптимизировано и быстро.

Как быстро? Для достаточно больших входов наравне с numba:

x = np.random.randint(0, 2, (10000, 10))

from timeit import timeit

shifter(x) # that should compile it, right?

print(timeit(lambda:shifter(x).astype(x.dtype), number=1000))
print(timeit(lambda:pp(x), number=1000))

Пример вывода:

0.8630472810036736
0.7336142909916816

Есть идеи?

10000