我能更有效地分割包含元组/空值混合的列吗?

7

我有一个简单的数据框:

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)

由于数据框切片的复制问题,通常会出现“A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc…” 错误信息。

我无法不去想我是否漏掉了什么。


2
不确定,但尝试将 df[get_rows][['x','y']] 更改为 df.loc[get_rows, ['x','y']] - Yuca
谢谢,但那行不通。Yuan的回答展示了正确的思考方式。 - angus l
4个回答

4

这里有另外一种方法 (注释内嵌):

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

1
谢谢,anky_91。这是一个不错的替代方案,但我已经将Yuan的标记为答案,因为它更接近我正在使用的方法。 - angus l

2

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


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

谢谢,Yuan。我感觉应该有一个简单的修复方法;我没想到会这么直接。 - angus l

1
另一个快速修复:

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

0

使用 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

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