Допустим, у меня есть DataFrame:
>>> df = pd.DataFrame({'a1':[1,2],'a2':[3,4],'b1':[5,6],'b2':[7,8],'c':[9,0]})
>>> df
a1 a2 b1 b2 c
0 1 3 5 7 9
1 2 4 6 8 0
>>>
И я хочу объединить (может быть, не объединить, а объединить) столбцы, в которых первая буква их имени равна, например, a1 и a2 и другие... но, как мы видим, есть столбец c, который сам по себе без каких-либо других похожие, поэтому я хочу, чтобы они не выдавали ошибки, а добавляли к ним NaNs.
Я хочу объединиться таким образом, чтобы он изменил широкий DataFrame на длинный DataFrame, в основном как модификацию широкого на длинный.
У меня уже есть решение проблемы, но дело в том, что оно очень неэффективно, я бы хотел более эффективное и быстрое решение (в отличие от моего :P), у меня в настоящее время есть цикл for и tryexcept (тьфу, звучит уже плохо ) код, например:
>>> df2 = pd.DataFrame()
>>> for i in df.columns.str[:1].unique():
try:
df2[i] = df[[x for x in df.columns if x[:1] == i]].values.flatten()
except:
l = df[[x for x in df.columns if x[:1] == i]].values.flatten().tolist()
df2[i] = l + [pd.np.nan] * (len(df2) - len(l))
>>> df2
a b c
0 1 5 9.0
1 3 7 0.0
2 2 6 NaN
3 4 8 NaN
>>>
Я хотел бы получить те же результаты с лучшим кодом.
@ GZ0 мой ответ дает точный результат. 3,7 занимают второе место по горизонтали, а 0 — второе по вертикали.
Я знаю. Я просто спрашиваю, действительно ли результат желателен или нет. Мне кажется, что было бы разумнее выровнять c=0. с (а, б) = (2, 6)






Я бы рекомендовал melt, а затем pivot. Чтобы устранить дубликаты, вам нужно выполнить поворот по столбцу с подсчетом.
u = df.melt()
u['variable'] = u['variable'].str[0] # extract the first letter
u.assign(count=u.groupby('variable').cumcount()).pivot('count', 'variable', 'value')
variable a b c
count
0 1.0 5.0 9.0
1 2.0 6.0 0.0
2 3.0 7.0 NaN
3 4.0 8.0 NaN
Это можно переписать как,
u = df.melt()
u['variable'] = [x[0] for x in u['variable']]
u.insert(0, 'count', u.groupby('variable').cumcount())
u.pivot(*u)
variable a b c
count
0 1.0 5.0 9.0
1 2.0 6.0 0.0
2 3.0 7.0 NaN
3 4.0 8.0 NaN
Если производительность имеет значение, вот альтернатива с pd.concat:
from operator import itemgetter
pd.concat({
k: pd.Series(g.values.ravel())
for k, g in df.groupby(operator.itemgetter(0), axis=1)
}, axis=1)
a b c
0 1 5 9.0
1 3 7 0.0
2 2 6 NaN
3 4 8 NaN
Результат немного отличается от ожидаемого OP благодаря комментарию @GZ0. Но я думаю, это не так важно.
@QuangHoang Да, я предполагал, что относительный порядок не важен для OP, но они могут захотеть уточнить.
Мы можем попробовать группировать столбцы (axis=1):
def f(g,a):
ret = g.stack().reset_index(drop=True)
ret.name = a
return ret
pd.concat( (f(g,a) for a,g in df.groupby(df.columns.str[0], axis=1)), axis=1)
выход:
a b c
0 1 5 9.0
1 3 7 0.0
2 2 6 NaN
3 4 8 NaN
Я знаю, что это не так хорошо, как использование Melt , но поскольку это вставляется в одну строку, если вам нужно более быстрое решение, попробуйте решение cs95.
df.groupby(df.columns.str[0],1).agg(lambda x : x.tolist()).sum().apply(pd.Series).T
Out[391]:
a b c
0 1.0 5.0 9.0
1 3.0 7.0 0.0
2 2.0 6.0 NaN
3 4.0 8.0 NaN
Используя rename и groupby.apply:
df = (df.rename(columns = dict(zip(df.columns, df.columns.str[:1])))
.groupby(level=0, axis=1, group_keys=False)
.apply(lambda x: pd.DataFrame(x.values.flat, columns=np.unique(x.columns))))
print(df)
a b c
0 1 5 9.0
1 3 7 0.0
2 2 6 NaN
3 4 8 NaN
Использование pd.concat с pd.melt и pd.groupby:
pd.concat([d.T.melt(value_name=k)[k] for k, d in df.groupby(df.columns.str[0], 1)], 1)
Выход:
a b c
0 1 5 9.0
1 3 7 0.0
2 2 6 NaN
3 4 8 NaN
Это решение дает аналогичный ответ cs95 и работает в два-три раза быстрее.
grouping = df.columns.map(lambda s: int(s[1:]) if len(s) > 1 else 1)
df.columns = df.columns.str[0] # Make a copy if the original dataframe needs to be retained
result = pd.concat((g for _, g in df.groupby(grouping, axis=1)),
axis=0, ignore_index=True, sort=False)
Выход
a b c
0 1 5 9.0
1 2 6 0.0
2 3 7 NaN
3 4 8 NaN
Вау, это самый быстрый
Ну, jezrael побил тебя миллисекундами :P
Спасибо. В моей среде тестирования мое решение немного быстрее, чем его, даже если я делаю копию исходных данных. Если я не сделаю копию, это будет на 50% быстрее.
Возможно, вы захотите взглянуть на мой комментарий под описанием проблемы и посмотреть, хотите ли вы добавить некоторые разъяснения о желаемом результате.
Я ОП, :P
Мой вывод немного отличается от желаемого вами вывода (например, cs95), и я думаю, что это более разумно, чем то, что вы предоставили. Я давал пояснения по условию задачи. Другие решения дают именно то, что вы просили.
Ваш результат такой же, как и мой желаемый, что более разумно, да. Я с тобой согласен
@ U9-Forward - ответ неверный, проверьте первый столбец - порядок другой. 1,2,3,4, но нужно 1,3,2,4
Используйте понимание словаря:
df = pd.DataFrame({i: pd.Series(x.to_numpy().ravel())
for i, x in df.groupby(lambda x: x[0], axis=1)})
print (df)
a b c
0 1 5 9.0
1 3 7 0.0
2 2 6 NaN
3 4 8 NaN
Почему с=0. выровнено с (a, b) = (3, 7) в новом фрейме данных, в то время как в исходном фрейме данных между ними нет связи?