如何拆分 Pandas 数据框中的一列元组?
How can I split a column of tuples in a Pandas dataframe?
我有一个Pandas数据框(这只是一小部分)
>>> 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-a
和 LCV-b
.
列替换 LCV
列
我该怎么做?
您可以通过在该列上执行 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)
。这也有效(因为它使每个元组组成一个系列,然后将其视为数据帧的一行),但它比 tolist
版本慢/使用更多内存,如其他答案所述(感谢 )。
在更大的数据集上,我发现 .apply()
比 pd.DataFrame(df['b'].values.tolist(), index=df.index)
慢几个数量级。
此性能问题已于 GitHub 关闭,但我不同意此决定:
performance issue - apply with pd.Series vs tuple #11615
它基于。
第二种解决方案的警告,
pd.DataFrame(df['b'].values.tolist())
是它会显式丢弃索引,并添加一个默认的顺序索引,而接受的答案
apply(pd.Series)
不会,因为应用的结果将保留行索引。虽然顺序最初是从原始数组中保留的,但 Pandas 将尝试匹配两个数据帧中的索引。
如果您尝试将行设置为数字索引数组,这可能非常重要,并且 Pandas 会自动尝试将新数组的索引与旧数组的索引相匹配,并导致一些失真顺序。
更好的混合解决方案是将原始数据帧的索引设置到新数据帧上,即
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 == object
的 pandas.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
来创建一个类似的iterable:
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
采用关键字参数,其中关键字是新的(或现有的)列名,值将是新列的值。您可以使用字典并使用 **
将其解压缩并将其作为关键字参数。
所以这是分配一个名为 'g'
的新列的聪明方法,它是 df.col.str
可迭代中的第一项,而 'h'
是 [= 中的第二项30=] 可迭代:
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)
extra 是另一种选择,使用来自
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')
我有一个Pandas数据框(这只是一小部分)
>>> 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-a
和 LCV-b
.
LCV
列
我该怎么做?
您可以通过在该列上执行 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)
。这也有效(因为它使每个元组组成一个系列,然后将其视为数据帧的一行),但它比 tolist
版本慢/使用更多内存,如其他答案所述(感谢
在更大的数据集上,我发现 .apply()
比 pd.DataFrame(df['b'].values.tolist(), index=df.index)
慢几个数量级。
此性能问题已于 GitHub 关闭,但我不同意此决定:
performance issue - apply with pd.Series vs tuple #11615
它基于
第二种解决方案的警告,
pd.DataFrame(df['b'].values.tolist())
是它会显式丢弃索引,并添加一个默认的顺序索引,而接受的答案
apply(pd.Series)
不会,因为应用的结果将保留行索引。虽然顺序最初是从原始数组中保留的,但 Pandas 将尝试匹配两个数据帧中的索引。
如果您尝试将行设置为数字索引数组,这可能非常重要,并且 Pandas 会自动尝试将新数组的索引与旧数组的索引相匹配,并导致一些失真顺序。
更好的混合解决方案是将原始数据帧的索引设置到新数据帧上,即
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 == object
的 pandas.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
来创建一个类似的iterable:
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
采用关键字参数,其中关键字是新的(或现有的)列名,值将是新列的值。您可以使用字典并使用 **
将其解压缩并将其作为关键字参数。
所以这是分配一个名为 'g'
的新列的聪明方法,它是 df.col.str
可迭代中的第一项,而 'h'
是 [= 中的第二项30=] 可迭代:
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)
extra 是另一种选择,使用来自 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')