У меня есть однострочный фрейм данных, как показано ниже.
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.
Предполагая, что '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