【问题标题】:pandas and category replacement熊猫和类别替换
【发布时间】:2015-04-17 21:46:20
【问题描述】:

我正在尝试通过用更短的分类值替换冗长的字段来减少约 300 个 csv 文件(大约十亿行)的大小。

我正在使用 pandas,并且我已经遍历每个文件以构建一个数组,其中包含我要替换的所有唯一值的所有。我不能单独对每个文件使用 pandas.factorize,因为我需要(例如)'3001958145' 映射到 file1.csv 和 file244.csv 上的相同值。我创建了一个数组,我想通过创建另一个递增整数数组来替换这些值 with

In [1]: toreplace = data['col1'].unique()
Out[1]: array([1000339602, 1000339606, 1000339626, ..., 3001958145, 3001958397,
   3001958547], dtype=int64)

In [2]: replacewith = range(0,len(data['col1'].unique()))
Out[2]: [0, 1, 2,...]

现在,对于需要迭代的每个文件,我如何有效地为每个相应的“替换”值交换“替换”变量?

处理类别的能力与 pandas 一样,我认为 必须 是一种可以实现这一点的方法,而我只是找不到。我为此编写的函数有效(它依赖于 pandas.factorized 输入,而不是我上面描述的排列),但它依赖于 replace 函数并遍历系列,所以它很慢。

def powerreplace(pdseries,factorized):
  i = 0
  for unique in pdseries.unique():
    print '%i/%i' % (i,len(pdseries.unique()))
    i=i+1
    pdseries.replace(to_replace=unique,
                     value=np.where(factorized[1]==unique)[0][0],
                     inplace=True)

谁能推荐一个更好的方法来做这件事?

【问题讨论】:

    标签: python arrays pandas categories


    【解决方案1】:

    这至少需要 pandas 0.15.0; (不过 .astype 语法在 0.16.0 中更友好一些,所以最好使用它)。这里是docs for categoricals

    进口

    In [101]: import pandas as pd
    In [102]: import string
    In [103]: import numpy as np    
    In [104]: np.random.seed(1234)
    In [105]: pd.set_option('max_rows',10)
    

    创建一个样本集来创建一些数据

    In [106]: uniques = np.array(list(string.ascii_letters))
    In [107]: len(uniques)
    Out[107]: 52
    

    创建一些数据

    In [109]: df1 = pd.DataFrame({'A' : uniques.take(np.random.randint(0,len(uniques)/2+5,size=1000000))})
    
    In [110]: df1.head()
    Out[110]: 
       A
    0  p
    1  t
    2  g
    3  v
    4  m
    
    In [111]: df1.A.nunique()
    Out[111]: 31
    
    In [112]: df2 = pd.DataFrame({'A' : uniques.take(np.random.randint(0,len(uniques),size=1000000))})
    
    In [113]: df2.head()
    Out[113]: 
       A
    0  I
    1  j
    2  b
    3  A
    4  m
    In [114]: df2.A.nunique()
    Out[114]: 52
    

    所以我们现在有 2 个要分类的帧;第一帧恰好没有完整的类别集。这是故意的;您不必预先知道完整的设置。

    将 A 列转换为属于 Categorical 的 B 列

    In [116]: df1['B'] = df1['A'].astype('category')
    
    In [118]: i = df1['B'].cat.categories
    
    In [124]: i
    Out[124]: Index([u'A', u'B', u'C', u'D', u'E', u'a', u'b', u'c', u'd', u'e', u'f', u'g', u'h', u'i', u'j', u'k', u'l', u'm', u'n', u'o', u'p', u'q', u'r', u's', u't', u'u', u'v', u'w', u'x', u'y', u'z'], dtype='object')
    

    如果我们迭代处理这些帧,我们会使用第一个开始。为了获得每个连续的,我们将对称差与现有集合相加。这使类别保持相同的顺序,因此当我们分解时,我们会得到相同的编号方案。

    In [119]: cats = i.tolist() + i.sym_diff(df2['A'].astype('category').cat.categories).tolist()
    

    我们现在已经找回了原来的集合

    In [120]: (np.array(sorted(cats)) == sorted(uniques)).all()
    Out[120]: True
    

    将下一帧 B 列设置为分类,但我们指定类别,因此在分解时使用相同的值

    In [121]: df2['B'] = df2['A'].astype('category',categories=cats)
    

    为了证明这一点,我们从每个代码中选择代码(分解后的映射)。这些代码匹配; df2 有一个附加代码(因为 Z 在第二帧而不是第一帧)。

    In [122]: df1[df1['B'].isin(['A','a','z','Z'])].B.cat.codes.unique()
    Out[122]: array([30,  0,  5])
    
    In [123]: df2[df2['B'].isin(['A','a','z','Z'])].B.cat.codes.unique()
    Out[123]: array([ 0, 30,  5, 51])
    

    然后您可以简单地存储代码来代替对象 dtyped 数据。

    请注意,将这些序列化为 HDF5 实际上非常有效,因为分类是本机存储的,请参阅 here

    请注意,我们正在创建一种非常节省内存的方式来存储这些数据。注意 [154] 中的内存使用情况,object dtype 实际上越高,字符串越长,因为这只是指针的内存;实际值存储在堆上。而 [155] 是所有已使用的内存。

    In [153]: df2.dtypes
    Out[153]: 
    A      object
    B    category
    dtype: object
    
    In [154]: df2.A.to_frame().memory_usage()
    Out[154]: 
    A    8000000
    dtype: int64
    
    In [155]: df2.B.to_frame().memory_usage()
    Out[155]: 
    B    1000416
    dtype: int64
    

    【讨论】:

    • 感谢您的深思熟虑的答复。这个解释非常清楚,非常有帮助!我不熟悉对称差分方法(它是 SQL JOIN!),但我知道这在未来会有什么用处。我也完全错过了类别 dtype 分解“引擎盖下”的事实,并且代码很容易访问。内存方面也很有意义。谢谢你指出。我很高兴能够实施这种方法!
    【解决方案2】:

    首先,让我们创建一些随机的“分类”数据。

    # Create some data
    random_letters = list('ABCDEFGHIJ')
    s_int = pd.Series(np.random.random_integers(0, 9, 100))
    s = pd.Series([random_letters[i] for i in s_int])
    >>> s.unique()
    array(['J', 'G', 'D', 'C', 'F', 'B', 'H', 'A', 'I', 'E'], dtype=object)
    

    现在我们将创建唯一类别到整数的映射。]

    # Create a mapping of integers to the relevant categories.
    mapping = {k: v for v, k in enumerate(s.unique())}
    
    >>> mapping
    {'A': 7,
     'B': 5,
     'C': 3,
     'D': 2,
     'E': 9,
     'F': 4,
     'G': 1,
     'H': 6,
     'I': 8,
     'J': 0}
    

    然后我们使用列表推导将类别替换为其映射的整数(下划线赋值表示未使用的虚拟变量)。

    _ = [s.replace(cat, mapping[cat], inplace=True) for cat in mapping]
    
    >>> s.head()
    0    0
    1    1
    2    2
    3    3
    4    4
    dtype: int64
    

    如果您想逆向获取原始类别:

    reverse_map = {k: v for v, k in mapping.iteritems()}
    
    reverse_map
    {0: 'J',
     1: 'G',
     2: 'D',
     3: 'C',
     4: 'F',
     5: 'B',
     6: 'H',
     7: 'A',
     8: 'I',
     9: 'E'}
    
    _ = [s.replace(int, reverse_map[int], inplace=True) for int in reverse_map]
    
    >>> s.head()
    0    J
    1    G
    2    D
    3    C
    4    F
    dtype: object
    

    【讨论】:

    • 这确实有效,而且比我的尝试更有效,但 Jeff 的解决方案更快,因为它依赖于 pandas 的 category dtype 的内置功能。
    • 我也应该提到这一点,但正如他所说,它需要 Pandas 0.15+。
    猜你喜欢
    • 2023-03-06
    • 2019-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-18
    • 2016-12-30
    • 2013-04-15
    • 2015-09-13
    相关资源
    最近更新 更多