Python 熊猫和类别替换

Python 熊猫和类别替换,python,arrays,pandas,categories,Python,Arrays,Pandas,Categories,我试图通过用较短的、分类的值替换冗长的字段来减少约300个csv文件(约10亿行)的大小 我正在使用pandas,并且我已经遍历了每个文件来构建一个数组,其中包含我试图替换的所有唯一值。我不能对每个文件单独使用pandas.factorize,因为我需要(例如)“3001958145”映射到file1.csv和file244.csv上的相同值。我已经创建了一个数组,我想通过创建另一个递增整数数组来替换这些值 In [1]: toreplace = data['col1'].unique() Ou

我试图通过用较短的、分类的值替换冗长的字段来减少约300个csv文件(约10亿行)的大小

我正在使用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”变量中有效地交换我需要迭代的每个文件的每个对应的“toreReplace”值

由于熊猫在处理分类方面的能力,我认为一定有一种方法可以实现这一点,而我就是找不到。我为此编写的函数可以工作(它依赖于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)

有人能推荐一种更好的方法吗?

这至少需要0.15.0;(然而,
.astype
语法在0.16.0中更为友好,因此最好使用它)。这是你的电话号码

进口

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
创建一些数据

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
我们现在有两个框架要分类;第一帧的类别恰好少于完整的类别集。这是故意的;你不必事先知道完整的设置

将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
将next frames B列设置为categorical,但我们指定了categories,因此在分解时使用相同的值

In [121]: df2['B'] = df2['A'].astype('category',categories=cats)
为了证明这一点,我们从每一个代码中选择代码(分解映射)。这些代码匹配;df2有一个附加代码(因为Z在第二帧中,但不是第一帧)

然后,您可以简单地存储代码来代替对象数据类型数据

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

请注意,我们正在创建一种非常高效的存储这些数据的方法。注意到[154]中的内存使用,字符串越长,
对象
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

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

# 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}
然后,我们使用列表理解将类别替换为其映射的整数(下划线赋值表示未使用的伪变量)

如果您希望反转此过程并获取原始类别:

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+。感谢您深思熟虑的回复。这个解释非常清楚,非常有用!我不熟悉对称差分方法(这是一种SQL连接!),但我知道这在将来是多么有用。我还完全忽略了这样一个事实:categories dtype分解为“under-the-hood”,代码易于访问。记忆方面也很有意义。谢谢你指出这一点。我很高兴能实现这种方法!