pandas 和类别替换

pandas and category replacement

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

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

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,...]

现在,我如何有效地交换我的 'replacewith' 变量,以便为我需要遍历的每个文件的每个相应 'toreplace' 值交换?

与 pandas 处理类别一样有能力,我假设 是一种可以实现这一点的方法,我只是可以'找不到。我编写的用于执行此操作的函数(它依赖于 pandas.factorized 输入而不是我上面描述的排列),但它依赖于替换函数并遍历系列,所以它非常慢。

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)

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

这至少需要 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 列转换为分类的 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)

为了证明这一点,我们 select 每个代码(分解图)。这些代码匹配; 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

首先,让我们创建一些随机 'categorical' 数据。

# 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