将行值作为列值获取

9

我有一个单行数据框,像下面这样:

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

我希望获得一个像下面的数据框:
```html

我希望获得一个像下面的数据框:

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

这里的逻辑是: a. 将有包含 TP/VR/TV 的 3 个列名。因此在 ID 中,我们有 1、2 和 3(可以通过从列名中提取值或仅使用范围来生成) b. TP1 值进入“Price”列的第一行,TP2 值进入“Price”列的第二行,依此类推 c. 对于 VR 和 TV 相同。值进入“Net”和“Range”列。 d. 列“Num”、“TR”和“TR=Tag”对结果不相关。
我尝试过 df.filter(regex='TP').stack()。我得到了所有 'TP' 列,可以通过索引访问单个值 ([0], [1], [2]),但我无法将它们直接放入一列中。
我也想知道是否有更简单的方法来实现这个目标。

这个回答解决了你的问题吗?如何将特定列移动到行中? - cottontail
5个回答

12

假设 '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

5

让我们创建一个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

4

pivot_wider(参见mozway的回答)从纯pandas角度来看,可能是最好的选择,但如果您需要更多的灵活性,也可以使用meltpivot

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方法来自mozway

(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毫秒。

Umar.H的答案使用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微秒

如果你不介意额外的导入,可以使用pivot_wider函数提供的速度和优雅的语法,参考sammywemmy的回答

(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微秒。

值得注意的是,这个数据集可能太小而无需关注速度,并且在更大的数据集上顺序可能不同。


2
一种选择是使用pyjanitor中的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

1

如果我理解正确,您可以使用以下方法:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接