Допустим, у нас есть следующий фрейм данных pandas:
df = pd.DataFrame({'a': {0: 3.0, 1: 2.0, 2: None}, 'b': {0: 10.0, 1: None, 2: 8.0}, 'c': {0: 4.0, 1: 2.0, 2: 6.0}})
a b c
0 3.0 10.0 4.0
1 2.0 NaN 2.0
2 NaN 8.0 6.0
Мне нужно получить фрейм данных с именами столбцов всех значений, отличных от NaN, для каждой строки. Я знаю, что могу сделать следующее, что приведет к ожидаемому результату:
df2 = df.apply(lambda x: pd.Series(x.dropna().index), axis=1)
0 1 2
0 a b c
1 a c NaN
2 b c NaN
К сожалению, это довольно медленно с большими наборами данных. Есть ли более быстрый способ?
Получение индексов строк ненулевых значений каждого столбца также может работать, так как мне просто нужно будет перенести входной фрейм данных. Спасибо.
Используйте numpy:
m = df.notna()
a = m.mul(df.columns).where(m).to_numpy()
out = pd.DataFrame(a[np.arange(len(a))[:,None], np.argsort(~m, axis=1)],
index=df.index)
Выход:
0 1 2
0 a b c
1 a c NaN
2 b c NaN
На 30 тыс. строк x 3 столбца:
# numpy approach
6.82 ms ± 1.56 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
# pandas apply
7.32 s ± 553 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
@Anoushiravan это преобразование индексатора диапазона 1D в одиночный столбец 2D. Это простой трюк для индексации массива в сочетании с np.argsort(~m, axis=1).
@mozway Я заметил, что иногда скрипт не сохраняет первоначальный порядок индексов. Есть ли способ исправить это? Спасибо
Здравствуйте, mozway, могу я спросить, что [:, None] делает в a[np.arange(len(a))[:, None]]? Фактически он превращает его в трехмерный массив. Но я не мог понять, зачем он нужен здесь.