Объединить столбцы pandas DataFrame, начинающиеся с одинаковых букв

Допустим, у меня есть 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
>>> 

Я хотел бы получить те же результаты с лучшим кодом.

Почему с=0. выровнено с (a, b) = (3, 7) в новом фрейме данных, в то время как в исходном фрейме данных между ними нет связи?

GZ0 07.06.2019 06:01

@ GZ0 мой ответ дает точный результат. 3,7 занимают второе место по горизонтали, а 0 — второе по вертикали.

Quang Hoang 07.06.2019 06:05

Я знаю. Я просто спрашиваю, действительно ли результат желателен или нет. Мне кажется, что было бы разумнее выровнять c=0. с (а, б) = (2, 6)

GZ0 07.06.2019 06:07
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
8
3
1 468
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Я бы рекомендовал 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. Но я думаю, это не так важно.

Quang Hoang 07.06.2019 06:07

@QuangHoang Да, я предполагал, что относительный порядок не важен для OP, но они могут захотеть уточнить.

cs95 07.06.2019 06:08

Мы можем попробовать группировать столбцы (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

Вау, это самый быстрый

U12-Forward 07.06.2019 08:18

Ну, jezrael побил тебя миллисекундами :P

U12-Forward 07.06.2019 08:22

Спасибо. В моей среде тестирования мое решение немного быстрее, чем его, даже если я делаю копию исходных данных. Если я не сделаю копию, это будет на 50% быстрее.

GZ0 07.06.2019 08:29

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

GZ0 07.06.2019 08:30

Я ОП, :P

U12-Forward 07.06.2019 08:31

Мой вывод немного отличается от желаемого вами вывода (например, cs95), и я думаю, что это более разумно, чем то, что вы предоставили. Я давал пояснения по условию задачи. Другие решения дают именно то, что вы просили.

GZ0 07.06.2019 08:34

Ваш результат такой же, как и мой желаемый, что более разумно, да. Я с тобой согласен

U12-Forward 07.06.2019 08:37

@ U9-Forward - ответ неверный, проверьте первый столбец - порядок другой. 1,2,3,4, но нужно 1,3,2,4

jezrael 07.06.2019 08:49
Ответ принят как подходящий

Используйте понимание словаря:

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

Другие вопросы по теме