Получить значения строки как значения столбца

У меня есть однострочный фрейм данных, как показано ниже.

Num     TP1(USD)    TP2(USD)    TP3(USD)    VReal1(USD)     VReal2(USD)     VReal3(USD)     TiV1 (EUR)  TiV2 (EUR)  TiV3 (EUR)  TR  TR-Tag
AA-24   0       700     2100    300     1159    2877    30       30     47      10  5

Я хочу получить фрейм данных, подобный приведенному ниже

ID  Price   Net     Range
1   0       300     30
2   700     1159    30
3   2100    2877    47

Логика здесь в том, что а. будет 3 столбца с именами, которые содержат TP/VR/TV. Таким образом, в идентификаторе у нас есть 1, 2 и 3 (их можно сгенерировать, извлекая значение из имен столбцов или просто используя диапазон для заполнения) б. Значение TP1 входит в первую строку столбца «Цена», значение TP2 входит во вторую строку столбца «Цена» и так далее. в. То же самое для VR и ТВ. Значения входят в столбцы «Чистая» и «Диапазон». д. Столбцы «Число», «TR» и «TR=Tag» не имеют отношения к результату.

Я пробовал df.filter(regex='TP').stack(). Я получаю весь столбец «TP» и могу получить доступ к отдельным значениям по индексу ([0], [1], [2]). Я не мог получить их все в колонку напрямую.

Я также задавался вопросом, может ли быть более простой способ сделать это.

🤔 А знаете ли вы, что...
Python поддерживает параллельное и асинхронное программирование с помощью модулей asyncio и multiprocessing.


9
608
5

Ответы:

Решено

Предполагая, что 'Num' является уникальным идентификатором, вы можете использовать pandas.wide_to_long:

pd.wide_to_long(df, stubnames=['TP', 'VR', 'TV'], i='Num', j='ID')

или, для вывода ближе к вашему:

out = (pd
 .wide_to_long(df, stubnames=['TP', 'VR', 'TV'], i='Num', j='ID')
 .reset_index('ID')
 .drop(columns=['TR', 'TR-Tag'])
 .rename(columns = {'TP': 'Price', 'VR': 'Net', 'TV': 'Range'})
 )

выход:

       ID  Price   Net  Range
Num                          
AA-24   1      0   300     30
AA-24   2    700  1159     30
AA-24   3   2100  2877     47
обновленный ответ
out = (pd
 .wide_to_long(df.set_axis(df.columns.str.replace(r'\(USD\)$', '', regex=True),
                           axis=1),
               stubnames=['TP', 'VReal', 'TiV'], i='Num', j='ID')
 .reset_index('ID')
 .drop(columns=['TR', 'TR-Tag'])
 .rename(columns = {'TP': 'Price', 'VReal': 'Net', 'TiV': 'Range'})
 )

выход:

       ID  Price   Net  Range
Num                          
AA-24   1      0   300     30
AA-24   2    700  1159     30
AA-24   3   2100  2877     47

pivot_wider (см. ответ мозвея), вероятно, лучше всего здесь с точки зрения чистых панд, но если вам нужна большая гибкость, вы также можете melt и pivot:

import pandas as pd

# recreating your dataframe
df = pd.DataFrame(['AA-24', '0', '700', '2100', '300', '1159', '2877', '30', '30', '47', '10', '5'], 
                  index= ['Num', 'TP1(USD)', 'TP2(USD)', 'TP3(USD)', 'VReal1(USD)', 'VReal2(USD)', 'VReal3(USD)', 'TiV1(EUR)', 'TiV2(EUR)', 'TiV3(EUR)', 'TR', 'TR-Tag']).T

# reshaping the data
(df.melt(id_vars=['Num','TR', 'TR-Tag'])
 .assign(col=lambda x: x['variable'].str[:2], idx=lambda x: x['variable'].str.extract("([0-9])"))
 .pivot(values='value', columns='col', index='idx')
 .rename(columns = {'TP': 'Price', 'VR': 'Net', 'Ti': 'Range'})
)

Как ни странно, это также быстрее, чем wide_to_long. Сравнительный анализ дает 7,76 мс ± 841 мкс на цикл для этого метода.

wide_to_longподход со стороны мозвея:

(pd
 .wide_to_long(df.set_axis(df.columns.str.replace(r'\([A-Z]{3}\)$', '', regex=True),
                           axis=1),
               stubnames=['TP', 'VReal', 'TiV'], i='Num', j='ID')
 .reset_index('ID')
 .drop(columns=['TR', 'TR-Tag'])
 .rename(columns = {'TP': 'Price', 'VReal': 'Net', 'TiV': 'Range'})
 )

тесты на 30,4 мс ± 3,07 мс на цикл на моей машине.

Ответ Умара.Х с использованием stack быстрее, чем оба:

df1 = df.filter(regex='TP|VR|TV')
df1.columns = df1.columns\
     .str.replace('(\d+)', r' \1' ,regex=True).str.split(' ',expand=True)
df1.stack(1).rename(columns = {'TP': 'Price', 'VR': 'Net', 'TV': 'Range'})

Выполняется со скоростью 6,07 мс ± 156 мкс на цикл

Если вы не возражаете против дополнительного импорта, ответ sammywemmy с использованием pivot_wider pyjanitor предлагает скорость и элегантный синтаксис.

(df
.select_columns('TP*', 'VR*', 'Ti*')
.pivot_longer(index = None, 
              names_to = ('.value', 'ID'), 
              names_pattern = ('(.+)(\d).+'))
.rename(columns = {'TP':'Price', 'VReal':'Net', 'TiV':'Range'})
)

тесты при 11,2 мс ± 229 мкс на цикл

и подход к шаблону имен:

df.pivot_longer(index = None, 
                names_to = ('Price', 'Net', 'Range'), 
                names_pattern = ('TP.*', 'VR.*', 'Ti.*'), 
                ignore_index = False)

является самым быстрым из протестированных, со скоростью 3,53 мс ± 95 мкс на цикл.

(Стоит отметить, что этот набор данных, вероятно, слишком мал, чтобы заботиться о скорости, и порядок может быть другим в больших наборах данных)


давайте создадим Multiindex, а затем используем .stack

df1 = df.filter(regex='TP|VR|TV')
#i couldn't figure out to split by 
#word\number without creating an additional whitespace split.
df1.columns = df1.columns\
     .str.replace('(\d+)', r' \1' ,regex=True).str.split(' ',expand=True)

#or more succinctly.
df1.columns = pd.MultiIndex.from_frame(df1.columns.str.extract('(\D+)(\d+)'))   

print(df1)

  TP              VR              TV
   1    2     3    1     2     3   1   2   3
0  0  700  2100  300  1159  2877  30  30  47

df1.stack(1).rename(columns = {'TP': 'Price', 'VR': 'Net', 'TV': 'Range'})
    
     Price  Range   Net
0 1      0     30   300
  2    700     30  1159
  3   2100     47  2877

IIUC, вы можете использовать:

df = pd.DataFrame({'TP1':[0], 'TP2':[700], 'TP3':[2100], 'VR1':[300], 'VR2':[1159], 'VR3':[2877], 'TV1':[30], 'TV2':[30], 'TV3':[47]})

pd.wide_to_long(df.reset_index(), ["TP", "VR", "TV"], i = "index", j = "Nr").droplevel('index').rename(columns = {'TP': 'Price', 'VR': 'Net', 'TV': 'Range'})

Результат:

    Price   Net  Range
Nr                    
1       0   300     30
2     700  1159     30
3    2100  2877     47

Один из вариантов — с pivot_longer из пиянитор:

# pip install pyjanitor
import pandas as pd
import janitor

(df
.select_columns('TP*', 'VR*', 'Ti*')
.pivot_longer(index = None, 
              names_to = ('.value', 'ID'), 
              names_pattern = ('(.+)(\d).+'))
.rename(columns = {'TP':'Price', 'VReal':'Net', 'TiV':'Range'})
)
  ID  Price   Net  Range
0  1      0   300     30
1  2    700  1159     30
2  3   2100  2877     47

В приведенном выше решении шаблон регулярного выражения используется для извлечения соответствующих подметок в столбцах; .value определяет, какие из подметок останутся заголовками.

Другое решение, которое может быть полезным, — передать список регулярных выражений в параметр names_pattern:

df.pivot_longer(index = None, 
                names_to = ('Price', 'Net', 'Range'), 
                names_pattern = ('TP.*', 'VR.*', 'Ti.*'), 
                ignore_index = False)

   Price   Net  Range
0      0   300     30
0    700  1159     30
0   2100  2877     47