#python #pandas #transpose #melt
Вопрос:
У меня есть df1, который выглядит так:
year site 1 2 3 year site 1 2 3 year site 1 2 3 year site 1 2 3
1991 A 4.1 5.9 4.1 1991 B 3.3 4.1 4.1 1991 C 4.1 0.6 4.1 1991 D 4.1 4.1 4.1
1992 A 6.2 5.7 6.2 1992 B 6.2 7.1 6.2 1992 C 6.2 6.2 6.2 1992 D 6.2 9.5 7.4
1993 A 2.6 1.9 4.7 1993 B 2.6 6.2 2.6 1993 C 5.4 8.3 2.6 1993 D 0.4 2.6 2.6
И у меня возникли проблемы с переносом столбцов, которые являются месяцами (1,2,3), в строки для каждого сайта, чтобы мой измененный df1 или df2 выглядел так:
year month Site A Site B Site C Site D
1991 1 4.1 3.3 4.1 4.1
1991 2 5.9 4.1 0.6 4.1
1991 3 4.1 4.1 4.1 4.1
1992 1 6.2 6.2 6.2 6.2
1992 2 5.7 7.1 6.2 9.5
1992 3 6.2 6.2 6.2 7.4
1993 1 2.6 2.6 5.4 0.4
1993 2 1.9 6.2 8.3 2.6
1993 3 4.7 2.6 2.6 2.6
Я пробовал использовать «расплав» и «стек», но я не понимаю, как ссылаться на повторяющиеся месяцы (1,2,3). Спасибо,
Комментарии:
1. пожалуйста, предоставьте воспроизводимый образец кадра данных
2. хорошо, конечно — я добавил исходные данные и надеюсь, что этого будет достаточно.
Ответ №1:
Попробуйте выполнить следующее, используя нарезку и изменение формы жесткого индекса:
#Create input dataframe
np.random.seed(0)
df = pd.concat([pd.DataFrame({'year':[1991, 1992, 1993],
'site':[i]*3,
1:np.round(np.random.randint(2,8,3) np.random.random(3),1),
2:np.round(np.random.randint(2,8,3) np.random.random(3),1),
3:np.round(np.random.randint(2,8,3) np.random.random(3),1)}) for i in [*'ABC']], axis=1)
# index slice columns of the dataframe
df_out = pd.concat([df.iloc[:,i:i 5] for i in range(0,df.shape[1],5)])
# Reshape with melt, set_index, and unstack
df_out = df_out.melt(['year', 'site'], var_name='month')
.set_index(['year', 'month', 'site'])['value']
.unstack('site').add_prefix('Site ')
.reset_index()
print(df_out)
Выход:
site year month Site A Site B Site C
0 1991 1 6.6 6.0 5.5
1 1991 2 7.3 5.5 7.6
2 1991 3 3.9 2.5 4.7
3 1992 1 7.5 2.1 5.6
4 1992 2 4.1 2.8 7.9
5 1992 3 2.1 3.8 2.1
6 1993 1 2.4 5.9 4.0
7 1993 2 6.3 3.1 2.7
8 1993 3 3.1 3.1 3.7
Комментарии:
1. Я собирался сделать позиционный, но я не хотел так сильно полагаться на структуру столбцов XD
2. Да, разумно не делать этого таким образом, если вы не уверены, что ваша структура данных исправна и безопасна.
3. Хотя это намного проще, и у него есть преимущество в том, что он рано выходит из строя, если ваши данные должны быть однородными, но не по какой-либо причине.
Ответ №2:
Мы можем создать новый уровень столбцов, с помощью которого заголовок каждого столбца будет сгруппирован позиционно groupby cumcount
. Преимущество этого заключается в том, что столбцы не должны находиться в определенном порядке, пока они имеют одинаковые имена.
Затем используйте stack
, чтобы собрать все отдельные группы в строки, set_index
исключить столбцы сайт и год, а затем stack
и unstack
сгруппировать по сайту вместо месяца:
# calculate new MultiIndex level
midx = pd.MultiIndex.from_arrays([
df.columns,
df.columns.to_series().groupby(level=0).cumcount()
])
new_df = (
df.set_axis(midx, axis=1) # replace columns
.stack() # Move all groups into rows
.set_index(['site', 'year']) # save site and year
.rename_axis(columns='Month') # rename column axis to Month
.stack() # Move all month columns to rows
.unstack(level='site') # Convert to site rows to columns
.add_prefix('Site ') # Add Prefix
.rename_axis(columns=None) # Remove Axis Name
.reset_index() # Restore Range Index
)
new_df
:
year Month Site A Site B Site C Site D
0 1991 1 A1 B1 C1 D1
1 1991 2 A2 B2 C2 D2
2 1991 3 A3 B3 C3 D3
3 1992 1 A1 B1 C1 D1
4 1992 2 A2 B2 C2 D2
5 1992 3 A3 B3 C3 D3
6 1993 1 A1 B1 C1 D1
7 1993 2 A2 B2 C2 D2
8 1993 3 A3 B3 C3 D3
Более рискованный подход заключается в том, чтобы reshape
DataFrame.values
основываться на заданном количестве уникальных столбцов ( 5
), тогда все остальное будет таким же, как указано выше:
unique_cols = df.columns.unique().tolist()
new_df = (
pd.DataFrame(
# reshape dataframe into len(unique_cols) columns
# and however many rows
df.values.reshape((-1, len(unique_cols))),
columns=unique_cols # restore column names
).set_index(['year', 'site'])
.rename_axis(columns='Month') # rename column axis to Month
.stack() # Move all month columns to rows
.unstack(level='site') # Convert to site rows to columns
.add_prefix('Site ') # Add Prefix
.rename_axis(columns=None) # Remove Axis Name
.reset_index() # Restore Range Index
)
new_df
:
year Month Site A Site B Site C Site D
0 1991 1 A1 B1 C1 D1
1 1991 2 A2 B2 C2 D2
2 1991 3 A3 B3 C3 D3
3 1992 1 A1 B1 C1 D1
4 1992 2 A2 B2 C2 D2
5 1992 3 A3 B3 C3 D3
6 1993 1 A1 B1 C1 D1
7 1993 2 A2 B2 C2 D2
8 1993 3 A3 B3 C3 D3
*Обратите внимание, что этот подход работает только в том случае, если структура фрейма данных может быть гарантирована, поскольку мы обходим все проверки целостности данных pandas путем изменения формы с помощью numpy.
Используемая Настройка:
from itertools import chain
import pandas as pd
sites = "ABCD"
df = pd.DataFrame(
chain.from_iterable([range(1991, 1994),
[f'{v}'] * 3,
[f'{v}1'] * 3,
[f'{v}2'] * 3,
[f'{v}3'] * 3] for v in sites)
).T
df.columns = ['year', 'site', 1, 2, 3] * len(sites)
Сокращенный df
:
year site 1 2 3 year site 1 ... 1 2 3 year site 1 2 3
0 1991 A A1 A2 A3 1991 B B1 ... C1 C2 C3 1991 D D1 D2 D3
1 1992 A A1 A2 A3 1992 B B1 ... C1 C2 C3 1992 D D1 D2 D3
2 1993 A A1 A2 A3 1993 B B1 ... C1 C2 C3 1993 D D1 D2 D3
Ответ №3:
Решение @HenryEcker является правильным и предпочтительным, особенно когда столбцы не структурированы так, как описано выше.
В приведенном ниже решении используется pivot_longer от pyjanitor, и предполагается, что столбцы упорядочены (если вы не уверены в порядке, решение @HenryEcker безопасно и выполняет работу с помощью уникальной идеи cumount):
# using Henry's data
# pip install pyjanitor
import janitor
import pandas as pd
df = df.rename(columns = str)
unique_columns = [*df.columns.unique()
(df.pivot_longer(names_to = unique_columns],
names_pattern = unique_columns)
.pivot('year', 'site')
.stack(level = 0)
.add_prefix('Site')
.rename_axis(columns = None,
index = ['year', 'month'])
.reset_index()
)
year month SiteA SiteB SiteC SiteD
0 1991 1 A1 B1 C1 D1
1 1991 2 A2 B2 C2 D2
2 1991 3 A3 B3 C3 D3
3 1992 1 A1 B1 C1 D1
4 1992 2 A2 B2 C2 D2
5 1992 3 A3 B3 C3 D3
6 1993 1 A1 B1 C1 D1
7 1993 2 A2 B2 C2 D2
8 1993 3 A3 B3 C3 D3