【问题标题】:Can I split this column containing a mix of tuples/None more efficiently?我可以更有效地拆分包含元组/无混合的列吗?
【发布时间】:2019-07-31 21:32:26
【问题描述】:

我有一个简单的 DataFrame:

import pandas as pd
df = pd.DataFrame({'id':list('abcd')})
df['tuples'] = df.index.map(lambda i:(i,i+1))

# outputs:
#   id  tuples
# 0  a  (0, 1)
# 1  b  (1, 2)
# 2  c  (2, 3)
# 3  d  (3, 4)

然后我可以非常简单地将元组列分成两列,例如

df[['x','y']] = pd.DataFrame(df.tuples.tolist())

# outputs:
#   id  tuples  x  y
# 0  a  (0, 1)  0  1
# 1  b  (1, 2)  1  2
# 2  c  (2, 3)  2  3
# 3  d  (3, 4)  3  4

这种方法也有效:

df[['x','y']] = df.apply(lambda x:x.tuples,result_type='expand',axis=1)

但是,如果我的 DataFrame 稍微复杂一些,例如

df = pd.DataFrame({'id':list('abcd')})
df['tuples'] = df.index.map(lambda i:(i,i+1) if i%2 else None)

# outputs:
#   id  tuples
# 0  a    None
# 1  b  (1, 2)
# 2  c    None
# 3  d  (3, 4)

然后第一种方法会抛出“列必须与键长度相同”(当然),因为有些行有两个值,有些没有,而我的代码预计会有两个。

我可以使用 .loc 创建单列,两次。

get_rows = df.tuples.notnull() # return rows with tuples

df.loc[get_rows,'x'] = df.tuples.str[0]
df.loc[get_rows,'y'] = df.tuples.str[1]

# outputs:
#   id  tuples    x    y
# 0  a    None  NaN  NaN
# 1  b  (1, 2)  1.0  2.0
# 2  c    None  NaN  NaN
# 3  d  (3, 4)  3.0  4.0

[旁白:有用的索引携带方式仅从右侧分配相关行,而无需指定它们。]

但是,我不能使用 .loc 一次创建两列,例如

# This isn't valid use of .loc
df.loc[get_rows,['x','y']] = df.loc[get_rows,'tuples'].map(lambda x:list(x))

因为它抛出错误“形状不匹配:形状 (2,2) 的值数组无法广播到形状 (2,) 的索引结果”。

我也不能用这个

df[get_rows][['x','y']] = df[get_rows].apply(lambda x:x.tuples,result_type='expand',axis=1)

因为它抛出通常的“一个值正在尝试在 DataFrame 的切片副本上设置。尝试使用 .loc...”

我忍不住想我错过了什么。

【问题讨论】:

  • 不确定,但请尝试将 df[get_rows][['x','y']] 更改为 df.loc[get_rows, ['x','y']]
  • 谢谢,但这不起作用。袁的回答显示了思考这个问题的正确方法。

标签: python pandas dataframe


【解决方案1】:

这是另一种方式(cmets inline):

c=df.tuples.astype(bool) #similar to df.tuples.notnull()
#create a dataframe by dropping the None and assign index as df.index where c is True
d=pd.DataFrame(df.tuples.dropna().values.tolist(),columns=list('xy'),index=df[c].index)
final=pd.concat([df,d],axis=1) #concat them both

  id  tuples    x    y
0  a    None  NaN  NaN
1  b  (1, 2)  1.0  2.0
2  c    None  NaN  NaN
3  d  (3, 4)  3.0  4.0

【讨论】:

  • 谢谢,anky_91。这是一个不错的选择,但我已将 Yuan 标记为答案,因为它更接近我正在使用的方法。
【解决方案2】:

df[get_rows] 是一个副本,将值设置为 df[get_rows][['x','y']] 不会更改基础数据。只需使用 df[['x','y']] 创建 now 列。

df = pd.DataFrame({'id':list('abcd')})

df['tuples'] = df.index.map(lambda i:(i,i+1) if i%2 else None)

get_rows = df.tuples.notnull()

df[['x','y']] = df[get_rows].apply(lambda x:x.tuples,result_type='expand',axis=1)

print(df)

  id  tuples    x    y
0  a    None  NaN  NaN
1  b  (1, 2)  1.0  2.0
2  c    None  NaN  NaN
3  d  (3, 4)  3.0  4.0

【讨论】:

  • 谢谢你,袁。我觉得应该有一个简单的解决方法;没想到这么简单。
【解决方案3】:

另一个快速修复:

pd.concat([df, pd.DataFrame(df.tuples.to_dict()).T], 
          axis=1)

返回:

  id  tuples     0     1
0  a    None  None  None
1  b  (1, 2)     1     2
2  c    None  None  None
3  d  (3, 4)     3     4

【讨论】:

    【解决方案4】:

    单线与itertools.zip_longest:

    In [862]: from itertools import zip_longest
    
    In [863]: new_columns = ['x', 'y']
    
    In [864]: df.join(df.tuples.apply(lambda x: pd.Series(dict(zip_longest(new_cols, [x] if pd.isnull(x) else list(x))))))
    Out[864]: 
      id  tuples    x    y
    0  a    None  NaN  NaN
    1  b  (1, 2)  1.0  2.0
    2  c    None  NaN  NaN
    3  d  (3, 4)  3.0  4.0
    

    甚至更简单:

    In [876]: f = lambda x: [x] * len(new_cols) if pd.isnull(x) else list(x)
    
    In [877]: df.join(pd.DataFrame(df.tuples.apply(f).tolist(), columns=new_cols))
    Out[877]: 
      id  tuples    x    y
    0  a    None  NaN  NaN
    1  b  (1, 2)  1.0  2.0
    2  c    None  NaN  NaN
    3  d  (3, 4)  3.0  4.0
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-04
      • 2020-11-25
      • 2010-11-13
      相关资源
      最近更新 更多