1、读取大文件遇到的困难

  ①用Office软件打不开
  ②用pd.read_csv读取超级慢,占用很多内存

2、大数据csv读入到内存

  ①快速读取多个分块对象(没有将实际数据读入,提数据时才读),得到一个可迭代对象TextFileReader:data = pd.read_csv(‘train.csv’,chunksize=块大小),数据处理和清洗都用分块的方式处理,大大降低内存使用

1
2
3
4
5
6
7
t=[]
for chunk_data in data: # 提取每一块数据
print(len(chunk_data.index)) #每一块的行数,都是5
for i in range(len(chunk_data.index)):
row_data = chunk_data.iloc[i] #是Series,索引是原数据的列标签
list_data = list(row_data) #得到列表
t.append(list_data) #嵌套列表

  ②读取需要的列:c = [‘列1’,’列2’]; data = pd.read_csv(‘train.csv’,usecols = c, chunksize=块大小)
  ③读取前5行数据:data = pd.read_csv(‘train.csv’,iterator=True),返回一个可迭代对象TextFileReader,再用get_chunk(5)方法返回前5行数据

3、将数据写入硬盘

  ①对于第一个分块:保留header信息,’w’模式写入 data.to_csv(‘result.csv’,index = False, mode = ‘w’)
  ②对于其它分块:去除header信息,’a’模式写入(不删除原文档,接着原文档后继续写) data.to_csv(‘result.csv’,index = False, header = False, mode = ‘a’)
  ③如果数据量较少,用pickle读取和写入

1
2
3
4
5
6
# 读取
def read_trainingSet(fileLoc):
import cPickle as pickle
with open(fileLoc, 'r') as f:
pack = pickle.load(f)
return pack[0], pack[1]

1
2
3
4
5
6
# 写入
def save_trainingSet(fileLoc, X, y):
import cPickle as pickle
pack = [X, y]
with open(fileLoc, 'w') as f:
pickle.dump(pack, f)

4、数据集的行、列合并

  list的列合并效果不好:list需要拆解每一行元素,并用extend拓展每一行的列元素;行合并可用append方法逐行读取
  DataFrame用concat方法合并:pd.concat([a,b],axis=1) axis=0表示行合并,跑起来会很慢(因为concat静态性,每次需要重新分配资源)

5、pandas降低内存占用

  用pd.concat合并数据框发生内存溢出的情况,思考如何降低内存的使用量。

1
2
3
#读取100万条数据
data = pd.read_csv('train.csv',iterator=True)
train = data.get_chunk(1000000)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>> train.info(memory_usage='deep')
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 1000000 to 1999999
Data columns (total 24 columns):
id 1000000 non-null float64
click 1000000 non-null int64
hour 1000000 non-null int64
C1 1000000 non-null int64
banner_pos 1000000 non-null int64
site_id 1000000 non-null object
site_domain 1000000 non-null object
site_category 1000000 non-null object
app_id 1000000 non-null object
app_domain 1000000 non-null object
app_category 1000000 non-null object
device_id 1000000 non-null object
device_ip 1000000 non-null object
device_model 1000000 non-null object
device_type 1000000 non-null int64
device_conn_type 1000000 non-null int64
C14 1000000 non-null int64
C15 1000000 non-null int64
C16 1000000 non-null int64
C17 1000000 non-null int64
C18 1000000 non-null int64
C19 1000000 non-null int64
C20 1000000 non-null int64
C21 1000000 non-null int64
dtypes: float64(1), int64(14), object(9)
memory usage: 672.3 MB

  可以看到该分块有100万行24列,pandas会自动为我们检测数据类型,发现其中有9列是object(是指有字符串或包含混合数据类型的情况)

(1)pandas如何将数据存储在内存中

  在pandas内部,不同数据类型都是分开存储的,相同数据类型的列会组织成同一个值块(比如IntBlock、ObjectBlock、FloatBlock),这些块没有保留原来列的名称(BlockManager类负责保留了行列索引与实际块之间的映射关系)
  看看各个数据类型平均内存使用量:

1
2
3
4
5
6
7
8
9
>> for dtype in ['float64','int64','object']:
s = train.select_dtypes(include=[dtype])
mb = s.memory_usage(deep=True).mean()
b = mb/1024**2
print("{}类型占用的平均内存是{:03.2f}MB".format(dtype,b))

float64类型占用的平均内存是3.81MB
int64类型占用的平均内存是7.12MB
object类型占用的平均内存是55.79MB

  发现object类使用的内存量最大。

(2)int类型的内存使用

  pandas内部将数值表示为numpy ndarrays,并把它们存储在内存的连续块中,这样存储模式占用的空间更少,也能让我们快速访问这些值。pandas的同一类型的每个值都用相同的字节数,而numpy ndarrays可以存储值的数量,所以pandas可以快速反馈一个数值列消耗的字节数。
  pandas的很多类型都有子类型,这些子类型可以用更少的字节来表示每个值,比如int8、int16、int32、int64子类型,名称中的数字就代表该类型表示值的位数,比如上面3个类型就分别使用了1、2、4、8个字节,int8类型的值可以表示-128到127之间的所有整数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#用numpy.info来检验每个整数子类型的最大值、最小值
>> int_types = ['uint8','int8','int16','int32','int64']
>> for it in int_types:
print(np.iinfo(it))

Machine parameters for uint8
------------------------------------------------------
min = 0 #无符号整型,可以更有效地处理只有正数值的列
max = 255
------------------------------------------------------

Machine parameters for int8
------------------------------------------------------
min = -128 #有符号整型
max = 127
------------------------------------------------------

Machine parameters for int16
------------------------------------------------------
min = -32768
max = 32767
------------------------------------------------------

Machine parameters for int32
------------------------------------------------------
min = -2147483648
max = 2147483647
------------------------------------------------------

Machine parameters for int64
------------------------------------------------------
min = -9223372036854775808
max = 9223372036854775807
------------------------------------------------------

(3)用子类型优化int数值列

  用df.select_dtypes来选择整数列,用pd.to_numeric()来对我们的数值类型进行downcast(向下转型)操作,对数据类型进行优化,并比较内存用量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def mem_usage(p):
if isinstance(p, pd.DataFrame):
b = p.memory_usage(deep=True).sum()
else:
b = p.memory_usage(deep=True)
mb = b/1024**2
return "{:03.2f}MB".format(mb)

chunkint = train.select_dtypes(include=['int64'])
convertint = chunkint.apply(pd.to_numeric, downcast='unsigned')

print(mem_usage(chunkint))
print(mem_usage(convertint))
# 返回106.81MB 28.61MB

1
2
3
4
5
6
7
8
9
compare_ints=pd.concat([chunkint.dtypes,convertint.dtypes],axis=1)
compare_ints.columns = ['before','after']
compare_ints.apply(pd.Series.value_counts)
#返回
    before after
uint8  NaN    6
uint16  NaN    6
uint32  NaN    1
uint64   14.0    1

  发现内存用量从106.81MB降到了8.61MB,很多unint64类型的数值数据,降到了uint8、unint16、unint32类型

(4)用子类型优化float浮点型列

1
2
3
4
5
6
7
8
9
10
11
12
13
chunkfloat = train.select_dtypes(include=['float64'])
convertfloat = chunkfloat.apply(pd.to_numeric, downcast='float')
print(mem_usage(chunkfloat))
print(mem_usage(convertfloat))
#返回7.63MB 3.81MB

compare_floats=pd.concat([chunkfloat.dtypes,convertfloat.dtypes],axis=1)
compare_floats.columns = ['before','after']
compare_floats.apply(pd.Series.value_counts)
#返回
    before after
float32 NaN   1.0
float64 1.0   NaN

  浮点数从float64降到了float32,让内存用量降低一半

(5)查看整体内存用量变化

  为原始数据框创建一个副本,用优化后的列替换原来的列,看看整体内存用量。

1
2
3
4
5
6
optdf = train.copy()
optdf[convertint.columns] = convertint
optdf[convertfloat.columns] = convertfloat
print(mem_usage(train))
print(mem_usage(optdf))
#返回672.34MB 590.32MB

  尽管我们极大地减少了数值列的内存用量,但整体的内存用量仅减少了12%,我们大部分的收获都将来自对object类型的优化。

(6)pandas中字符串的存储方式

  object类型表示用字符串对象,一部分原因是numpy不支持缺失字符串类型。因为python是一种高级的解释型语言,对内存中存储的值没有细粒度的控制能力,这导致字符串的存储方式碎片化,会消耗很多内存,访问速度也很慢。
  object列中每个元素实际上都是一个指针,包含了实际值在内存中的位置。尽管每个指针只占用1字节的内存,但如果每个字符串都单独存储,也会占用很大的空间。证明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#查看单个字符串
from sys import getsizeof
s1 = 'working out'
s2 = 'memory usage for'
s3 = 'strings in python is fun!'
s4 = 'strings in python is fun!'
for s in [s1,s2,s3,s4]:
print(getsizeof(s))
#返回60 65 74 74

#将这4个字符串存储在pd.series中,查看字符串大小
objseries = pd.Series([s1,s2,s3,s4])
objseries.apply(getsizeof)
#返回
0  60
1  65
2  74
3  74
dtype: int64
#说明存在series的字符串大小,与单独存储的字符串大小一样

(7)用Categoricals优化object类型

  pandas在0.15版引入了Categoricals,Category类型在底层用int子类型表示字符串。pandas使用一个单独的映射字典,将数值映射到原始字符串,只要当一个列中包含有限的值,这种方法就有用。来看看object类型中每种类型的不同值数量。

1
2
objdata = train.select_dtypes(include=['object']).copy()
objdata.describe()

统计
  拿出一个object列(site_category)来看看,将它转换成categorical类型时发生了什么。

1
2
3
4
s = train.site_category
sc = s.astype('category')
print(s.head())
print(sc.head())

转换
  所以除了这一列的类型发生改变外,数据看起来完全一样,再来看看这背后发生了什么:

1
2
3
4
5
6
7
8
sc.head().cat.codes     #返回category类型用来表示每个值的整型值
#返回
0 2
1 2
2 2
3 2
4 0
dtype: int8

  可看到每个不同值都被分配了一个整型值,该列现在的基本数据类型是int8(如果有缺失值,会被设置为-1)。再来看看转换前后的内存使用量对比:

1
2
3
print(mem_usage(s))
print(mem_usage(sc))
#返回61.99MB 0.96MB

  注意,category类型不能进行算术运算,也不适用于不同值数量 > 总数量50%的列(内存会占用更多),现在编写一个循环函数来迭代检查每一个object列中不同值的数量是否少于50%,如果是就将其转换成category类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
conver = pd.DataFrame()
for col in objdata.columns:
num_unique = len(objdata[col].unique())
num_total = len(objdata[col])
if num_unique/num_total < 0.5:
conver.loc[:,col] = objdata[col].astype('category')
else:
conver.loc[:,col] = objdata[col]

print(mem_usage(objdata))
print(mem_usage(conver))
#返回557.90MB 57.24MB

compare_obj=pd.concat([objdata.dtypes,conver.dtypes],axis=1)
compare_obj.columns = ['before','after']
compare_obj.apply(pd.Series.value_counts)
#返回
    before after
object  78.0   NaN
category NaN   78.0

1
2
3
4
#与数值类型的列结合
optdf[conver.columns] = conver
mem_usage(optdf)
#返回89.66MB(由590.32MB降到89.66MB)

(8)读入数据时指定最优的数据类型

  pd.read_csv()指定参数dtype,它接受一个字典(key是列名称,value是类型)

1
2
3
4
5
6
7
8
9
#将每一列的最优类型存储在dict中
dtypes = optdf.dtypes
col = dtypes.index
opttype = [i.name for i in dtypes.values]
col_dict = dict(zip(col,opttype))

train1 = train.astype(col_dict)
mem_usage(train1)
#返回89.66MB