替换类别数据 (pandas)

Replacing Category Data (pandas)

我有一些包含多个类别列的大文件。类别也是一个很大的词,因为这些基本上是 descriptions/partial 个句子。

以下是每个类别的唯一值:

Category 1 = 15 
Category 2 = 94
Category 3 = 294
Category 4 = 401

Location 1 = 30
Location 2 = 60 

然后甚至还有具有重复数据(名字、姓氏、ID 等)的用户。

我正在考虑以下解决方案来缩小文件大小:

1) 创建一个文件,用唯一的整数匹配每个类别

2) 创建一个地图(有没有办法通过读取另一个文件来做到这一点?就像我创建一个 .csv 并将其作为另一个数据帧加载然后匹配它?或者我真的必须输入它最初?)

3) 基本上做一个连接(VLOOKUP),然后用长对象名称删除旧列

pd.merge(df1, categories, on = 'Category1', how = 'left') 
del df1['Category1']

在这种情况下,人们通常会做什么?这些文件非常大。 60 列,大部分数据都是长的、重复的类别和时间戳。从字面上看,根本没有数字数据。这对我来说很好,但由于共享驱动器 space 分配超过几个月,共享文件几乎是不可能的。

Pandas 有一个 Categorical 数据类型可以做到这一点。它基本上将类别映射到幕后的整数。

Internally, the data structure consists of a categories array and an integer array of codes which point to the real value in the categories array.

文档是 here

要在保存到 csv 时受益于 Categorical dtype,您可能需要遵循以下过程:

  1. 将您的类别定义提取到单独的数据帧/文件中
  2. 将您的分类数据转换为 int 代码
  3. 将转换后的 DataFrame 与定义数据帧一起保存到 csv

当您需要再次使用它们时:

  1. 从 csv 文件恢复数据帧
  2. 将具有 int 代码的数据框映射到类别定义
  3. 将映射列转换为分类

为了说明这个过程:

制作示例数据框:

df = pd.DataFrame(index=pd.np.arange(0,100000))
df.index.name = 'index'
df['categories'] = 'Category'
df['locations'] = 'Location'
n1 = pd.np.tile(pd.np.arange(1,5), df.shape[0]/4)
n2 = pd.np.tile(pd.np.arange(1,3), df.shape[0]/2)
df['categories'] = df['categories'] + pd.Series(n1).astype(str)
df['locations'] = df['locations'] + pd.Series(n2).astype(str)
print df.info()

    <class 'pandas.core.frame.DataFrame'>
Int64Index: 100000 entries, 0 to 99999
Data columns (total 2 columns):
categories    100000 non-null object
locations     100000 non-null object
dtypes: object(2)
memory usage: 2.3+ MB
None

注意大小:2.3+ MB - 这大约是您的 csv 文件的大小。 现在将这些数据转换为 Categorical:

df['categories'] = df['categories'].astype('category')
df['locations'] = df['locations'].astype('category')
print df.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 100000 entries, 0 to 99999
Data columns (total 2 columns):
categories    100000 non-null category
locations     100000 non-null category
dtypes: category(2)
memory usage: 976.6 KB
None

注意内存使用量下降到 976.6 KB 但是,如果您现在将其保存为 csv:

df.to_csv('test1.csv')

...你会在文件中看到这个:

index,categories,locations
0,Category1,Location1
1,Category2,Location2
2,Category3,Location1
3,Category4,Location2

这意味着 'Categorical' 已转换为字符串以保存在 csv 中。 因此,让我们在保存定义后删除 Categorical 数据中的标签:

categories_details = pd.DataFrame(df.categories.drop_duplicates(), columns=['categories'])
print categories_details

      categories
index           
0      Category1
1      Category2
2      Category3
3      Category4

locations_details = pd.DataFrame(df.locations.drop_duplicates(), columns=['locations'])
print locations_details

       index           
0      Location1
1      Location2

现在将 Categorical 转换为 int dtype:

for col in df.select_dtypes(include=['category']).columns:
    df[col] = df[col].cat.codes
print df.head()

       categories  locations
index                       
0               0          0
1               1          1
2               2          0
3               3          1
4               0          0

print df.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 100000 entries, 0 to 99999
Data columns (total 2 columns):
categories    100000 non-null int8
locations     100000 non-null int8
dtypes: int8(2)
memory usage: 976.6 KB
None

将转换后的数据保存到 csv 并注意该文件现在只有没有标签的数字。 文件大小也将反映此更改。

df.to_csv('test2.csv')

index,categories,locations
0,0,0
1,1,1
2,2,0
3,3,1

同时保存定义:

categories_details.to_csv('categories_details.csv')
locations_details.to_csv('locations_details.csv')

当您需要恢复文件时,从 csv 个文件加载它们:

df2 = pd.read_csv('test2.csv', index_col='index')
print df2.head()

       categories  locations
index                       
0               0          0
1               1          1
2               2          0
3               3          1
4               0          0

print df2.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 100000 entries, 0 to 99999
Data columns (total 2 columns):
categories    100000 non-null int64
locations     100000 non-null int64
dtypes: int64(2)
memory usage: 2.3 MB
None

categories_details2 = pd.read_csv('categories_details.csv', index_col='index')
print categories_details2.head()

      categories
index           
0      Category1
1      Category2
2      Category3
3      Category4

print categories_details2.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 4 entries, 0 to 3
Data columns (total 1 columns):
categories    4 non-null object
dtypes: object(1)
memory usage: 64.0+ bytes
None

locations_details2 = pd.read_csv('locations_details.csv', index_col='index')
print locations_details2.head()

       locations
index           
0      Location1
1      Location2

print locations_details2.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 2 entries, 0 to 1
Data columns (total 1 columns):
locations    2 non-null object
dtypes: object(1)
memory usage: 32.0+ bytes
None

现在使用mapint编码数据替换为类别描述并将其转换为Categorical:

df2['categories'] = df2.categories.map(categories_details2.to_dict()['categories']).astype('category')
df2['locations'] = df2.locations.map(locations_details2.to_dict()['locations']).astype('category')
print df2.head()

      categories  locations
index                      
0      Category1  Location1
1      Category2  Location2
2      Category3  Location1
3      Category4  Location2
4      Category1  Location1

print df2.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 100000 entries, 0 to 99999
Data columns (total 2 columns):
categories    100000 non-null category
locations     100000 non-null category
dtypes: category(2)
memory usage: 976.6 KB
None

注意内存使用回到我们第一次将数据转换为 Categorical 时的状态。 如果你需要多次重复这个过程,自动化这个过程应该不难。

这是一种在单个 .csv 中保存包含分类列的数据框的方法:

Example:
------      -------
Fatcol      Thincol: unique strings once, then numbers
------      -------
"Alberta"   "Alberta"
"BC"        "BC"
"BC"        2   -- string 2
"Alberta"   1   -- string 1
"BC"        2
...

The "Thincol" on the right can be saved as is in a .csv file,
and expanded to the "Fatcol" on the left after reading it in;
this can halve the size of big .csv s with repeated strings.

Functions
---------
fatcol( col: Thincol ) -> Fatcol, list[ unique str ]
thincol( col: Fatcol ) -> Thincol, dict( unique str -> int ), list[ unique str ]

Here "Fatcol" and "Thincol" are type names for iterators, e.g. lists:
    Fatcol: list of strings
    Thincol: list of strings or ints or NaN s
If a `col` is a `pandas.Series`, its `.values` are used.

这将 700M .csv 压缩为 248M -- 但是 write_csv 在我的 imac 上以 ~ 1 MB/sec 运行。