Pandas处理字符串列的最佳方式(不拆分)

zqdjd7g9  于 2023-02-11  发布在  其他
关注(0)|答案(3)|浏览(185)

问题简介

我在一个DataFrame列中使用字符串编码数据:

id             data
0  a   2;0;4208;1;790
1  b     2;0;768;1;47
2  c       2;0;92;1;6
3  d          1;0;341
4  e  3;0;1;2;6;4;132
5  f  3;0;1;1;6;3;492

数据表示系统中某些事件发生的次数。我们可以有256个不同的事件(每个事件都有从0 - 255范围内分配的数字ID)。通常,我们在一个测量周期中只有几个事件发生,存储全零没有意义。这就是为什么数据编码如下:第一个数字表示在测量期间发生了多少事件,然后每对包含event_id和counter。
例如:
"三;零;一;一;六;三;492 "是指:

  • 测量期间发生3起事件
  • ID = 0的事件发生1次
  • ID = 1的事件发生6次
  • ID = 3的事件发生492次
  • 其他事件没有发生

我需要将数据解码为单独的列。预期结果是DataFrame,如下所示:

id  data_0  data_1  data_2  data_3  data_4
0  a  4208.0   790.0     0.0     0.0     0.0
1  b   768.0    47.0     0.0     0.0     0.0
2  c    92.0     6.0     0.0     0.0     0.0
3  d   341.0     0.0     0.0     0.0     0.0
4  e     1.0     0.0     6.0     0.0   132.0
5  f     1.0     6.0     0.0   492.0     0.0

问题本身

我想出了下面的函数来做这件事:

def split_data(data: pd.Series):
    tmp = data.str.split(';', expand=True).astype('Int32').fillna(-1)
    tmp = tmp.apply(
        lambda row: {'{0}_{1}'.format(data.name,row[i*2-1]): row[i*2] for i in range(1,row[0]+1)},
        axis='columns',
        result_type='expand').fillna(0)
    return tmp

df = pd.concat([df, split_data(df.pop('data'))], axis=1)

问题是我有数百万行要处理,这需要很多时间。因为我没有太多的Pandas经验,我希望有人能帮助我更有效地执行这项任务的方式。

编辑-答案分析

好了,我把三个答案都拿出来,并进行了一些基准测试:)。我已经有了一个DataFrame(这很重要!)。正如预期的那样,它们都比我的代码快。例如,对于15行,1000次重复的时间:

  • 我的密码:0.5827s
  • Schalton代码:0.1138s
  • Shubham代码:0.2242s
  • SomeDudes的代码:0.2219

看来沙尔顿的密码赢了!
但是...对于1500行,50次重复:

  • 我的密码:31.1139
  • Schalton代码:2.4599s
  • Shubham代码:0.511s
  • SomeDudes的代码:17.15

我决定再检查一次,这一次只尝试了一次,但检查了150000行:

  • 我的密码:68.6798s
  • Schalton代码:6.3889s
  • Shubham代码:0.9520s
  • SomeDudes的代码:37.8837

有趣的事情发生了:随着DataFrame的大小变大,除了Shubham的版本需要更长的时间!两个最快的版本是Schalton的和Shubham的版本。这是起点问题!我已经有了现有的DataFrame,所以我必须将其转换为字典。字典本身处理真的很快。但是转换需要时间。Shubham的解决方案或多或少与大小无关! Schalton 's对于小数据集非常有效,但是由于转换为dict,对于大数据量它会变慢。另一个比较,这次是150000行,重复30次:

  • Schalton代码:170.1538s
  • Shubham代码:36.32s

然而,对于具有30000次重复的15行:

  • Schalton代码:50.4997s
  • Shubham代码:74.0916s

总结

最后,Schalton版本和Shubham版本之间的选择取决于用例:

  • 对于大量的小 Dataframe (或在开始时使用字典),使用Schalton的解决方案
  • 对于非常大的 Dataframe ,使用Shubham的解决方案。

如上所述,我有大约100万行的数据集,因此我将采用Shubham的答案。

wtzytmuj

wtzytmuj1#

编号

pairs = df['data'].str.extractall(r'(?<!^)(\d+);(\d+)')
pairs = pairs.droplevel(1).pivot(columns=0, values=1).fillna(0)

df[['id']].join(pairs.add_prefix('data_'))

解释

Extract使用正则表达式模式的所有pairs

0     1
  match         
0 0      0  4208
  1      1   790
1 0      0   768
  1      1    47
2 0      0    92
  1      1     6
3 0      0   341
4 0      0     1
  1      2     6
  2      4   132
5 0      0     1
  1      1     6
  2      3   492

旋转pairs以调整为所需格式

0     0    1  2    3    4
0  4208  790  0    0    0
1   768   47  0    0    0
2    92    6  0    0    0
3   341    0  0    0    0
4     1    0  6    0  132
5     1    6  0  492    0

将重新整形的pairs Dataframe 与id列重新连接

id data_0 data_1 data_2 data_3 data_4
0  a   4208    790      0      0      0
1  b    768     47      0      0      0
2  c     92      6      0      0      0
3  d    341      0      0      0      0
4  e      1      0      6      0    132
5  f      1      6      0    492      0
gwo2fgha

gwo2fgha2#

我会避免在Pandas中处理这些数据,假设您有其他格式的数据,我会将其解析为字典列表,然后将其加载到Pandas中。

import pandas as pd
from typing import Dict

data = {
    "a": "2;0;4208;1;790",
    "b": "2;0;768;1;47",
    "c": "2;0;92;1;6",
    "d": "1;0;341",
    "e": "3;0;1;2;6;4;132",
    "f": "3;0;1;1;6;3;492"
}

def get_event_counts(event_str: str, delim: str = ";") -> Dict[str, int]:
    """
    given an event string return a dictionary of events
    """
    EVENT_COUNT_INDEX = 0
    
    split_event = event_str.split(delim)
    event_count = int(split_event[EVENT_COUNT_INDEX])
    
    events = {
        split_event[index*2+1]: int(split_event[index*2+2]) for index in range(event_count - 1 // 2)
    }
    
    return events

data_records = [{"id": k, **get_event_counts(v)} for k,v in data.items()]

print(pd.DataFrame(data_records))

id     0      1    2      4      3
0  a  4208  790.0  NaN    NaN    NaN
1  b   768   47.0  NaN    NaN    NaN
2  c    92    6.0  NaN    NaN    NaN
3  d   341    NaN  NaN    NaN    NaN
4  e     1    NaN  6.0  132.0    NaN
5  f     1    6.0  NaN    NaN  492.0

如果你把当前的df作为输入,你可以尝试这样做:

def process_starting_dataframe(starting_dataframe: pd.DataFrame) -> pd.DataFrame:
    """
    Create a new dataframe from original input with two columns "id" and "data
    """
    data_dict = starting_df.T.to_dict()
    data_records = [{"id": i['id'], **get_event_counts(i['data'])} for i in data_dict.values()]
    
    return pd.DataFrame(data_records)
68bkxrlz

68bkxrlz3#

一个更有效的方法是从data构造dict。您观察到拆分字符串中的替代值是如何成为键和值的吗?
然后应用pd.Seriesfillna(0)以获得包含数据所需的所有列的 Dataframe 。
然后你就可以继续了。
代码:

df_data = df['data'].apply(
    lambda x:dict(zip(x.split(';')[1::2], x.split(';')[2::2]))).apply(pd.Series).fillna(0)
df_data.columns = df_data.columns.map('data_{}'.format)
df = pd.concat([df.drop('data',axis=1), df_data], axis=1)

输出:

id data_0 data_1 data_2 data_4 data_3
0  a   4208    790      0      0      0
1  b    768     47      0      0      0
2  c     92      6      0      0      0
3  d    341      0      0      0      0
4  e      1      0      6    132      0
5  f      1      6      0      0    492

如果您需要排序列,只需执行以下操作:

df = df[sorted(df.columns)]

相关问题