Python 如何有效地扩展/展平数据帧
我有一个数据集,在它的一列上,每个元素都是一个列表。 我想把它展平,这样每个列表元素都有自己的一行 我设法用Python 如何有效地扩展/展平数据帧,python,pandas,Python,Pandas,我有一个数据集,在它的一列上,每个元素都是一个列表。 我想把它展平,这样每个列表元素都有自己的一行 我设法用iterrows、dict和append(见下文)解决了这个问题,但我的真实DF太大,速度太慢。 有没有办法让事情变得更快 我可以考虑用另一种格式(可能是层次的DF)替换每个元素的列(如果是分层的DF),如果这更合理的话。 编辑:我有许多专栏,其中一些可能会在将来更改。我唯一确定的是我有字段栏。这就是为什么我在解决方案中使用dict 一个最简单的示例,创建要使用的df: import St
iterrows
、dict
和append
(见下文)解决了这个问题,但我的真实DF太大,速度太慢。
有没有办法让事情变得更快
我可以考虑用另一种格式(可能是层次的DF)替换每个元素的列(如果是分层的DF),如果这更合理的话。 编辑:我有许多专栏,其中一些可能会在将来更改。我唯一确定的是我有字段栏。这就是为什么我在解决方案中使用
dict
一个最简单的示例,创建要使用的df:
import StringIO
df = pd.read_csv(StringIO.StringIO("""
id|name|fields
1|abc|[qq,ww,rr]
2|efg|[zz,xx,rr]
"""), sep='|')
df.fields = df.fields.apply(lambda s: s[1:-1].split(','))
print df
结果df:
id name fields
0 1 abc [qq, ww, rr]
1 2 efg [zz, xx, rr]
我的(慢)解决方案:
new_df = pd.DataFrame(index=[], columns=df.columns)
for _, i in df.iterrows():
flattened_d = [dict(i.to_dict(), fields=c) for c in i.fields]
new_df = new_df.append(flattened_d )
导致
id name fields
0 1.0 abc qq
1 1.0 abc ww
2 1.0 abc rr
0 2.0 efg zz
1 2.0 efg xx
2 2.0 efg rr
通过将
pandas.Series
应用于字段,然后合并到id
和name
,可以将字段中的列表拆分为多列,如下所示:
cols = df.columns[df.columns != 'fields'].tolist() # adapted from @jezrael
df = df[cols].join(df.fields.apply(pandas.Series))
然后,您可以使用set_index
和stack
融化生成的新列,然后重置索引:
df = df.set_index(cols).stack().reset_index()
最后,删除reset_index生成的冗余列,并将生成的列重命名为“field”:
您可以使用numpy
以获得更好的性能:
这两种解决方案主要使用
另一个解决方案:
new_df = pd.DataFrame(index=[], columns=df.columns)
for _, i in df.iterrows():
flattened_d = [dict(i.to_dict(), fields=c) for c in i.fields]
new_df = new_df.append(flattened_d )
df[['id','name']]。值
将列转换为numpy数组
并按进行复制,然后将值堆叠在列表中
按进行添加
更通用的解决方案是过滤掉列字段
,然后将其添加到数据帧
构造函数中,因为总是最后一列:
cols = df.columns[df.columns != 'fields'].tolist()
print (cols)
['id', 'name']
df1 = pd.DataFrame(np.column_stack((df[cols].values.
repeat(list(map(len,df.fields)),axis=0),np.hstack(df.fields))),
columns=cols + ['fields'])
print (df1)
id name fields
0 1 abc qq
1 1 abc ww
2 1 abc rr
3 2 efg zz
4 2 efg xx
5 2 efg rr
如果您的CSV有数千行,则使用字符串方法(如下)
可能比使用\u iterows
或使用\u repeat
的速度更快:
与
因此,对于10000行CSV,使用字符串方法的比使用iterrows的快600倍以上,比使用重复的快一点
通常,只有当数据处于同一位置时,才能进行快速NumPy/Pandas操作
本地NumPy数据类型(例如int64
或float64
或字符串)
在jig启动的数据帧中列出(非本机NumPy数据类型)——您是被迫的
使用Python速度循环来处理列表
因此,为了提高性能,您需要避免将列表放置在数据帧中
使用_string_方法
将字段
数据作为字符串加载:
df = pd.read_csv(StringIO(csv), sep='|', dtype=None)
并避免使用apply
方法(通常与普通Python循环一样慢):
相反,它使用更快的矢量化字符串方法将字符串分解为
单独列:
fields = (df['fields'].str.extract(r'\[(.*)\]', expand=False)
.str.split(r',', expand=True))
一旦将字段放在单独的列中,就可以使用pd.melt
重塑形状
将数据帧转换为所需格式
pd.melt(df, id_vars=['id', 'name'], value_name='field')
顺便说一句,您可能会感兴趣地看到,只需稍微修改一下,使用\u iterrows
就可以和使用\u repeat
一样快。我使用_itertuples
显示中的更改。
df.itertuples
往往略快于df.iterrows
,但差别很小。大部分速度增益是通过避免在for循环中调用df.append
实现的,因为第一个命令失败。错误是MergeError:No common columns to execute on
Yep抱歉,我的意思是使用join
,它根据索引值工作。我更正了我的答案,仍然不起作用。下面是结果(展平为一行):id name level_2 0 0 1 abc fields[qq,ww,rr]1 2 efg fields[zz,xx,rr]
此外,它看起来像是最后的reset_index
导致了一个无关的level_2
列,可以简单地删除(即df.drop('level_2',axis=1,inplace=True)
)但这并不能解决主要问题,即DF不是扩展的Hanks。我有很多列,有些列将来可能会更改。我唯一能确定的是我有fields列。有没有办法重构您的解决方案,s.t。我不必手动键入“id”、“name”?这就是为什么我在解决方案中使用dict()是的,我认为第二种解决方案更好。给我一分钟。它工作起来很快。你能在正文中解释一下构造函数的输入吗?注意列表是多余的。map(len,df.fields)已经返回了一个列表抱歉,这对于python 3是必需的,在python 2中您可以省略它。谢谢。我喜欢您的方法,但是在我的例子中,原始数据实际上不是来自CSV,所以这不是问题。
import pandas as pd
try: from cStringIO import StringIO # for Python2
except ImportError: from io import StringIO # for Python3
def using_string_methods(csv):
df = pd.read_csv(StringIO(csv), sep='|', dtype=None)
other_columns = df.columns.difference(['fields']).tolist()
fields = (df['fields'].str.extract(r'\[(.*)\]', expand=False)
.str.split(r',', expand=True))
df = pd.concat([df.drop('fields', axis=1), fields], axis=1)
result = (pd.melt(df, id_vars=other_columns, value_name='field')
.drop('variable', axis=1))
result = result.dropna(subset=['field'])
return result
def using_iterrows(csv):
df = pd.read_csv(StringIO(csv), sep='|')
df.fields = df.fields.apply(lambda s: s[1:-1].split(','))
new_df = pd.DataFrame(index=[], columns=df.columns)
for _, i in df.iterrows():
flattened_d = [dict(i.to_dict(), fields=c) for c in i.fields]
new_df = new_df.append(flattened_d )
return new_df
def using_repeat(csv):
df = pd.read_csv(StringIO(csv), sep='|')
df.fields = df.fields.apply(lambda s: s[1:-1].split(','))
cols = df.columns[df.columns != 'fields'].tolist()
df1 = pd.DataFrame(np.column_stack(
(df[cols].values.repeat(list(map(len,df.fields)),axis=0),
np.hstack(df.fields))), columns=cols + ['fields'])
return df1
def using_itertuples(csv):
df = pd.read_csv(StringIO(csv), sep='|')
df.fields = df.fields.apply(lambda s: s[1:-1].split(','))
other_columns = df.columns.difference(['fields']).tolist()
data = []
for tup in df.itertuples():
data.extend([[getattr(tup, col) for col in other_columns]+[field]
for field in tup.fields])
return pd.DataFrame(data, columns=other_columns+['field'])
csv = 'id|name|fields'+("""
1|abc|[qq,ww,rr]
2|efg|[zz,xx,rr]"""*10000)
df = pd.read_csv(StringIO(csv), sep='|', dtype=None)
df.fields = df.fields.apply(lambda s: s[1:-1].split(','))
fields = (df['fields'].str.extract(r'\[(.*)\]', expand=False)
.str.split(r',', expand=True))
pd.melt(df, id_vars=['id', 'name'], value_name='field')