【问题标题】:Python datatable/pandas reshaping problemPython数据表/熊猫重塑问题
【发布时间】:2021-04-03 14:56:19
【问题描述】:

我需要重塑我的 df。

这是我的输入 df:

import pandas as pd
import datatable as dt

DF_in = dt.Frame(name=['name1', 'name1', 'name1', 'name1', 'name2', 'name2', 'name2', 'name2'],
             date=['2021-01-01', '2021-01-02', '2021-01-03', '2021-01-04', '2021-01-05', '2021-01-06', '2021-01-07', '2021-01-08'],
             type=['a', 'b', 'a', 'b', 'b', 'a', 'b', 'a'],
             value=[1, 2, 3, 4, 5, 6, 7, 8])

   | name   date        type  value
-- + -----  ----------  ----  -----
 0 | name1  2021-01-01  a         1
 1 | name1  2021-01-02  b         2
 2 | name1  2021-01-03  a         3
 3 | name1  2021-01-04  b         4
 4 | name2  2021-01-05  b         5
 5 | name2  2021-01-06  a         6
 6 | name2  2021-01-07  b         7
 7 | name2  2021-01-08  a         8

这是所需的输出df:

DF_out = dt.Frame(name=['name1', 'name1', 'name2', 'name2'],
              date_a=['2021-01-01', '2021-01-03', '2021-01-06', '2021-01-08'],
              date_b=['2021-01-02', '2021-01-04', '2021-01-07', None],
              value_a=[1, 3, 6, 8],
              value_b=[2, 4, 7, None])

   | name   date_a      date_b      value_a  value_b
-- + -----  ----------  ----------  -------  -------
 0 | name1  2021-01-01  2021-01-02        1        2
 1 | name1  2021-01-03  2021-01-04        3        4
 2 | name2  2021-01-06  2021-01-07        6        7
 3 | name2  2021-01-08  NA                8       NA

如有必要,可以将数据表帧转换为熊猫数据帧:

DF_in = DF_in.to_pandas()

转换:

  • 这是一个分组转换。分组列是“名称”。
  • df 已排序
  • 每组的行数不同,可以是偶数,也可以是奇数
  • 如果组中的第一行在“类型”列中有“b”,则必须将其删除(例如:DF_in 中的第 4 行)
  • 也可能组中的最后一行在“type”列中有一个“a”,此行不应丢失(例如:DF_in 中的第 7 行)

我希望这个解释是可以理解的。

提前谢谢你

【问题讨论】:

  • 鉴于name1 是前两行中name 的值,为什么2021-01-04 匹配2021-01-03 而不是2021-01-01 的日期和43 而不是1 以获取价值?这仅仅是接近吗?
  • 确实是接近。 df 已排序,如果列“type”中包含值“a”的行包含值“b”,则该行应与其下方的行匹配。这必须按组进行。它变得有点困难,因为每组的行数并不总是均匀的,而且它们并不总是以值“a”开始并以“type”列中的值“b”结束。
  • @k_n_c 你对这个问题有什么想法吗?

标签: python pandas dataframe reshape py-datatable


【解决方案1】:

非常感谢大家的回答。与此同时,我开发了一个仅使用数据表包的解决方案,针对当前限制使用了一些解决方法:

  1. 定义一个函数来为相邻行创建 id:1,1,2,2,...
  2. 创建包含行索引的列 ID
  3. 获取要删除的行的 ID 作为列表
  4. 从所有行 ID 中减去要删除的行 ID
  5. 根据剩余的行 ID 对 Frame 进行子集
  6. 获取每组的行数
  7. 对每个组使用该函数并使用行数作为输入, 创建一个包含所有结果的列表(与子集后的帧长度相同)。将此绑定到框架
  8. 根据列类型(“a”或“b”)创建两个子框架
  9. 在 df1 上加入 df2

代码:

import math
import datatable as dt
from datatable import dt, f, by, update, join

DF_in = dt.Frame(name=['name1', 'name1', 'name1', 'name1', 'name2', 'name2', 'name2', 'name2'],
                 date=['2021-01-01', '2021-01-02', '2021-01-03', '2021-01-04', '2021-01-05', '2021-01-06', '2021-01-07', '2021-01-08'],
                 type=['a', 'b', 'a', 'b', 'b', 'a', 'b', 'a'],
                 value=[1, 2, 3, 4, 5, 6, 7, 8])



def group_id(n):
    l = [x for x in range(0, math.floor(n / 2))]
    l = sorted(l * 2)
    if n % 2 != 0:
        try:
            l.append(l[-1] + 1)
        except IndexError:
            l.append(0)
    return l


DF_in['id'] = range(DF_in.nrows)
first_row = f.id==dt.min(f.id)
row_eq_b = dt.first(f.type)=="b"
remove_rows = first_row & row_eq_b
DF_in[:, update(remove_rows = ~remove_rows), 'name']
DF_in = DF_in[f[-1]==1, :-1]
group_count = DF_in[:, {"Count": dt.count()}, by('name')][:, 'Count'].to_list()[0]
group_id_column = []

for x in group_count:
    group_id_column = group_id_column + group_id(x)

DF_in['group_id'] = dt.Frame(group_id_column)
df1 = DF_in[f.type == 'a', ['name', 'date', 'value', 'group_id']]
df2 = DF_in[f.type == 'b', ['name', 'date', 'value', 'group_id']]

df2.key = ['name', 'group_id']
DF_out = df1[:, :, join(df2)]
DF_out.names = {'date': 'date_a', 'value': 'value_a', 'date.0': 'date_b', 'value.0': 'value_b'}

DF_out[:, ['name', 'date_a', 'date_b', 'value_a', 'value_b']]

   | name   date_a      date_b      value_a  value_b
-- + -----  ----------  ----------  -------  -------
 0 | name1  2021-01-01  2021-01-02        1        2
 1 | name1  2021-01-03  2021-01-04        3        4
 2 | name2  2021-01-06  2021-01-07        6        7
 3 | name2  2021-01-08  NA                8       NA

【讨论】:

  • @sammywemmy,非常感谢您清理解决方案 - 与原始代码相比,它只需要 30% 的时间即可运行。
  • 不客气@peter。希望数据表中添加更多功能以消除这些限制
【解决方案2】:

让我们使用数据框,所以首先加载数据

df = pd.DataFrame(dict(name=['name1', 'name1', 'name1', 'name1', 'name2', 'name2', 'name2', 'name2'],
             date=['2021-01-01', '2021-01-02', '2021-01-03', '2021-01-04', '2021-01-05', '2021-01-06', '2021-01-07', '2021-01-08'],
             type=['a', 'b', 'a', 'b', 'b', 'a', 'b', 'a'],
             value=[1, 2, 3, 4, 5, 6, 7, 8]))

那么在下面我们执行以下步骤

  • 摆脱第二个bs
  • 在“g”列中分配组号
  • 通过set_index + unstack 旋转表格
  • 将列重命名为所需的格式
  • 删除不需要的列
df1 = df[~((df['type'] == 'b') & (df['type'].shift() == 'b'))].copy()
df1['g'] = np.arange(len(df1))//2
df2 = df1.set_index(['g','type']).unstack(level=1)
df2.columns = ['_'.join(tup).rstrip('_') for tup in df2.columns.values]
df2.drop(columns = 'name_b').rename(columns = {'name_a':'name'})

输出

    name    date_a      date_b      value_a value_b
g                   
0   name1   2021-01-01  2021-01-02  1.0     2.0
1   name1   2021-01-03  2021-01-04  3.0     4.0
2   name2   2021-01-06  2021-01-07  6.0     7.0
3   name2   2021-01-08  NaN         8.0     NaN

【讨论】:

  • 非常感谢您的回答。但是,您的解决方案的第一步对我的数据并不可靠。一组的最后一行可能包含一个“a”,而下一组的第一行可能包含一个“b”。在这种情况下,摆脱次要“b”将失败。
  • 另外,代码的第二行应该按组执行,因为组中的行数可能不均匀。
【解决方案3】:

数据表没有允许在垂直和水平位置之间翻转的重塑功能;因此,熊猫是您最好的选择。

以下是我对您的挑战的尝试:

    from datatable import dt
    import pandas as pd

    df = DF_in.to_pandas()

    (df
     .assign(temp = df.index, # needed for ranking
             b_first = lambda df: df.groupby('name')['type'].transform('first'))
     .assign(temp = lambda df: df.groupby('name')['temp'].rank())
      # get rid of rows in groups where b is first
     .query('~(temp==1 and b_first=="b")')
      # needed to get unique values in index when pivoting
     .assign(temp = lambda df: df.groupby(['name','type']).cumcount())
     .pivot(['name','temp'], ['type'], ['date','value'])
     .pipe(lambda df: df.set_axis(df.columns.to_flat_index(), axis='columns')
     .rename(columns = lambda df: "_".join(df)))
     .droplevel('temp')
     .reset_index()
      )

    name      date_a      date_b value_a value_b
0  name1  2021-01-01  2021-01-02       1       2
1  name1  2021-01-03  2021-01-04       3       4
2  name2  2021-01-06  2021-01-07       6       7
3  name2  2021-01-08         NaN       8     NaN

总结:

  • 过滤掉“b”是组中第一个条目的行

  • 为避免在旋转(重新索引)时由于重复索引而导致错误,请创建一个临时 cumcount 列

  • 其余的依赖于 pivot 和一些名称编辑(set_axis 和 rename 函数)。您可以使用来自pyjanitorpivot_wider 函数进一步抽象:

     # pip install pyjanitor
     import janitor
    
     (df
     .assign(temp = df.index, 
             b_first = lambda df: df.groupby('name')['type'].transform('first'))
     .assign(temp = lambda df: df.groupby('name')['temp'].rank())
     .query('~(temp==1 and b_first=="b")')
     .assign(temp = lambda df: df.groupby(['name','type']).cumcount())
     .pivot_wider(index=['name', 'temp'], 
                  names_from=['type'], 
                  values_from=['date','value'],   
                  names_sep="_",
                  names_from_position='last')
     .drop(columns='temp')
      )
    

【讨论】:

  • 嗨@sammywemmy,感谢您的回答。我会看看。我也想出了一个没有转换为熊猫的解决方案。我很好奇你的想法。
猜你喜欢
  • 2013-05-14
  • 2017-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-21
相关资源
最近更新 更多