如何在熊猫数据框中分割元组列?

我有一个熊猫数据框(这只是一个小片)

>>> d1
y norm test  y norm train  len(y_train)  len(y_test)  \
0    64.904368    116.151232          1645          549
1    70.852681    112.639876          1645          549


SVR RBF  \
0   (35.652207342877873, 22.95533537448393)
1  (39.563683797747622, 27.382483096332511)


LCV  \
0  (19.365430594452338, 13.880062435173587)
1  (19.099614489458364, 14.018867136617146)


RIDGE CV  \
0  (4.2907610988480362, 12.416745648065584)
1    (4.18864306788194, 12.980833914392477)


RF  \
0   (9.9484841581029428, 16.46902345373697)
1  (10.139848213735391, 16.282141345406522)


GB  \
0  (0.012816232716538605, 15.950164822266007)
1  (0.012814519804493328, 15.305745202851712)


ET DATA
0  (0.00034337162272515505, 16.284800366214057)  j2m
1  (0.00024811554516431878, 15.556506191784194)  j2m
>>>

我想分割所有包含元组的列。例如,我希望将列 LCV替换为列 LCV-aLCV-b

我该怎么做?

121723 次浏览

您可以通过对该列执行 pd.DataFrame(col.tolist())来实现这一点:

In [2]: df = pd.DataFrame({'a':[1,2], 'b':[(1,2), (3,4)]})


In [3]: df
Out[3]:
a       b
0  1  (1, 2)
1  2  (3, 4)


In [4]: df['b'].tolist()
Out[4]: [(1, 2), (3, 4)]


In [5]: pd.DataFrame(df['b'].tolist(), index=df.index)
Out[5]:
0  1
0  1  2
1  3  4


In [6]: df[['b1', 'b2']] = pd.DataFrame(df['b'].tolist(), index=df.index)


In [7]: df
Out[7]:
a       b  b1  b2
0  1  (1, 2)   1   2
1  2  (3, 4)   3   4

注意: 在早期版本中,这个答案推荐使用 df['b'].apply(pd.Series)而不是 pd.DataFrame(df['b'].tolist(), index=df.index)。这样也可以(因为它生成每个元组的 Series,然后将其视为数据帧的一行) ,但是它比 tolist版本更慢/使用更多内存,这里的其他答案也提到了这一点(感谢 到丹佛穆法)。

在大得多的数据集上,我发现 .apply()pd.DataFrame(df['b'].values.tolist(), index=df.index)慢几个数量级。

这个性能问题在 GitHub 中已经解决,尽管我不同意这个决定:

性能问题-适用于 pd.Series vs tuple # 11615

它是基于 这个答案的。

第二种解决方案的警告,

pd.DataFrame(df['b'].values.tolist())

它将显式放弃索引,并添加默认的顺序索引,而接受的答案

apply(pd.Series)

不会,因为申请的结果将保留行索引。虽然顺序最初是从原始数组保留,熊猫将尝试匹配的索引从两个数据框架。

如果您试图将行设置为数字索引数组,那么这一点非常重要,熊猫将自动尝试将新数组的索引与旧数组的索引相匹配,并导致排序中的一些失真。

一个更好的混合解决方案是将原始数据框架的索引设置为新的,即,

pd.DataFrame(df['b'].values.tolist(), index=df.index)

它将保持使用第二种方法的速度,同时确保结果上保留顺序和索引。

我认为更简单的方法是:

>>> import pandas as pd
>>> df = pd.DataFrame({'a':[1,2], 'b':[(1,2), (3,4)]})
>>> df
a       b
0  1  (1, 2)
1  2  (3, 4)
>>> df['b_a'] = df['b'].str[0]
>>> df['b_b'] = df['b'].str[1]
>>> df
a       b  b_a  b_b
0  1  (1, 2)    1    2
1  2  (3, 4)    3    4

dtype == objectpandas.Series对象可用的 str访问器实际上是可迭代的。

假设 pandas.DataFrame df:

df = pd.DataFrame(dict(col=[*zip('abcdefghij', range(10, 101, 10))]))


df


col
0   (a, 10)
1   (b, 20)
2   (c, 30)
3   (d, 40)
4   (e, 50)
5   (f, 60)
6   (g, 70)
7   (h, 80)
8   (i, 90)
9  (j, 100)

我们可以测试它是否是可迭代的:

from collections import Iterable


isinstance(df.col.str, Iterable)


True

然后我们可以像对待其他可迭代程序一样对它进行赋值:

var0, var1 = 'xy'
print(var0, var1)


x y

最简单的办法

因此,在一行中,我们可以分配两列:

df['a'], df['b'] = df.col.str


df


col  a    b
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

更快的解决方案

只是稍微复杂一点,我们可以使用 zip创建一个类似的迭代器:

df['c'], df['d'] = zip(*df.col)


df


col  a    b  c    d
0   (a, 10)  a   10  a   10
1   (b, 20)  b   20  b   20
2   (c, 30)  c   30  c   30
3   (d, 40)  d   40  d   40
4   (e, 50)  e   50  e   50
5   (f, 60)  f   60  f   60
6   (g, 70)  g   70  g   70
7   (h, 80)  h   80  h   80
8   (i, 90)  i   90  i   90
9  (j, 100)  j  100  j  100

插进去

也就是说,不要变异现有的 df

这是因为 assign接受关键字参数,其中关键字是新的(或现有的)列名,值将是新列的值。您可以使用一个 dictionary 并用 **解压缩它,让它充当关键字参数。

因此,这是一种聪明的方法,可以分配一个名为 'g'的新列,它是 df.col.str迭代表中的第一个项目,而 'h'df.col.str迭代表中的第二个项目:

df.assign(**dict(zip('gh', df.col.str)))


col  g    h
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

我的 list方法

带有现代列表内涵和可变拆箱。 注意: 也使用 join内联

df.join(pd.DataFrame([*df.col], df.index, [*'ef']))


col  g    h
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

变异的版本应该是

df[['e', 'f']] = pd.DataFrame([*df.col], df.index)

幼稚时间测试

短数据帧

使用上面定义的方法:

%timeit df.assign(**dict(zip('gh', df.col.str)))
%timeit df.assign(**dict(zip('gh', zip(*df.col))))
%timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh']))


1.16 ms ± 21.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
635 µs ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
795 µs ± 42.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
长数据框架

大10 ^ 3倍

df = pd.concat([df] * 1000, ignore_index=True)


%timeit df.assign(**dict(zip('gh', df.col.str)))
%timeit df.assign(**dict(zip('gh', zip(*df.col))))
%timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh']))


11.4 ms ± 1.53 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.1 ms ± 41.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.33 ms ± 35.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

额外的是另一个选项,使用来自 Https://opendataportal-lasvegas.opendata.arcgis.com/datasets/restaurant-inspections-open-data/explore

import pandas as pd
df = pd.read_csv('raw_data.csv', low_memory=False)
df[['latitude', 'longitude']] = df['Location_1'].str.extract(pat = '(-?\d+\.\d+),\s*(-?\d+\.\d+)')
df.to_csv('result.csv')