Numpy Pandas.concat的内存效率问题

Numpy Pandas.concat的内存效率问题

在处理数据时,选择适用的工具非常重要。在Python中,除了标准库中的数据结构和函数外,还有两个特别受欢迎的数据处理工具,它们是NumPy和Pandas。本文将讨论这两者的特点、应用场景和一些使用技巧。

阅读更多:Numpy 教程

NumPy

NumPy是Python中数据科学领域最常用的科学计算库,它提供了高效的数组和向量计算操作。NumPy的数组是一类由相同类型的元素组成的n维网格,它们是在内存块中连续存放的。NumPy中的各种函数可以高效地对这些数组进行各种操作,如加、减、乘、除、Slice等等。

下面是一个简单的例子,展示了NumPy的数组是如何工作的:

import numpy as np

a = np.array([1, 2, 3])      # 创建一个数组
print(type(a))            # 输出 "<class 'numpy.ndarray'>"
print(a.shape)            # 输出 "(3,)"
print(a[0], a[1], a[2])   # 输出 "1 2 3"
a[0] = 5                  # 修改数组中的元素
print(a)                  # 输出 "[5, 2, 3]"
Python

NumPy能够很好地支持快速而灵活的向量化计算,因此在应对数值数据处理和科学计算时,使用NumPy可以大大提高数据处理的效率。

Pandas

与NumPy相比,Pandas是一个更高级的数据处理工具,它的主要数据结构是Series和DataFrame,这两个工具都建立在NumPy之上提供了更高的抽象层次。Pandas有许多方便的操作,适合于处理结构化的数据,如SQL表格式数据。Pandas主要应用场景包括数据预处理、数据清洗和各种类型的统计分析。

Pandas的核心数据结构是DataFrame,它由行和列组成的,每列可以是不同的数据类型(数值、字符串、日期等等)。下面是一个简单的例子,展示了如何创建一个DataFrame对象:

import pandas as pd
import numpy as np

df = pd.DataFrame({
   '名字': ['张三', '李四', '王五', '赵六'],
   '年龄': [20, 25, 30, 35],
   '城市': ['北京', '上海', '广州', '深圳'],
   '职业': ['学生', '工程师', '教师', '医生']
})
Python

这里创建了一个包含四个列和四行的DataFrame,每列分别代表“名字”、“年龄”、“城市”和“职业”。Pandas可以方便地对这个DataFrame进行各种操作,如数据选择、数据过滤、数据排序、数据合并等等。

Pandas.concat的内存效率问题

虽然Pandas在数据预处理和清洗方面提供了很多方便的操作,但是它也有一些内存效率问题,其中最突出的是Pandas.concat函数。

Pandas.concat可以用来沿着一个轴(axis)将多个Pandas对象合并到一起,例如将多个DataFrame对象合并为一个DataFrame对象。具体的语法是:

pd.concat([obj1,obj2,],axis=0,join='outer',join_axes=None,ignore_index=False,copy=True)
Python

其中:

  • axis:合并的轴,0表示纵向(垂直合并),1表示横向(水平合并);
  • join:指定合并的方式,’inner’表示内连接,’outer’表示外连接;
  • ignore_index:是否忽略原对象的行索引用

据此我们可以以以下代码进行合并:

import pandas as pd

df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [3, 4], 'B': [5, 6]})

pd.concat([df1, df2])
Python

输出结果是:

   A  B
0  1  3
1  2  4
0  3  5
1  4  6
Python

尽管Pandas提供了便捷的函数来进行数据拼接,但是在拼接大型数据时,会消耗大量的内存资源,严重降低程序性能。这主要是因为Pandas.concat在执行时,会将需要拼接在一起的所有数据对象都放在内存中进行全部拼接,然后再将拼接后的结果放在磁盘上。因此,如果需要拼接的数据对象非常大,程序就容易发生“内存溢出”的错误。

这时,我们可以考虑使用一种更高效的方法——逐块读取和拼接数据,这是Numpy的优势所在。我们可以使用Numpy中的memmap对象以及divmod函数来实现这个目的。

import pandas as pd
import numpy as np

def concat_df_chunks(dfs, chunksize, filename):
    """
    按照指定块大小对多个DataFrame对象进行拼接
    :param dfs: 多个DataFrame对象组成的list
    :param chunksize: 每个块的大小
    :param filename: 存储的文件名
    """

    # 计算拼接后矩阵的形状
    n_cols = sum((df.shape[1] for df in dfs))
    n_rows = sum((df.shape[0] for df in dfs))

    # 计算每个块在矩阵中的位置
    block_starts = np.arange(n_rows // chunksize + 1) * chunksize

    # 使用Numpy memmap创建磁盘上的临时文件
    mmap_file = np.memmap(filename, dtype='float32', mode='w+',
                          shape=(n_rows, n_cols))

    # 逐块操作
    col_start = 0
    indexes = []
    for i, start in enumerate(block_starts):
        block_end = min(start + chunksize, n_rows)

        # 拼接数据块
        rows = []
        for df in dfs:
            rows.append(df.iloc[block_end-chunksize:block_end])

        block = pd.concat(rows, axis=1)

        # 将数据块保存到磁盘上的临时文件中
        mmap_file[start:block_end, col_start:col_start+block.shape[1]] = block.values

        # 保存索引位置
        indexes.append(block.index)

        # 更新列位置
        col_start += block.shape[1]

    # 构建最终的DataFrame对象
    result = pd.DataFrame(mmap_file, dtype='float32')

    # 恢复索引
    result.index = pd.concat(indexes)

    return result

# 使用示例
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [3, 4], 'B': [5, 6]})

result = concat_df_chunks([df1, df2], chunksize=1, filename='tmp.dat')
print(result)
Python

这里使用了memmap数组,它可以将数组数据保存在磁盘文件中,而不是保存在内存中。这可以使我们逐块加载和处理数据,从而减少内存压力。在我们的示例中,逐块读取和拼接数据的大小可以由chunksize参数控制。

总结

本文介绍了Python中两个常用的数据处理工具NumPy和Pandas,分析了它们的特点和应用场景。此外,还讨论了Pandas.concat函数的内存效率问题,并给出了基于Numpy的更高效的数据拼接方案。使用这种方法,我们可以逐块处理大量数据对象,避免内存溢出和其他进程争夺内存的问题。但是需要注意的是,代码需要精心设计和优化,否则也会影响程序性能。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程

登录

注册