如何在不使用 Pandas 的情况下将 Numpy 转换为 Parquet?

14

传统的numpy对象保存为parquet的方法是使用Pandas作为中间件。然而,我正在处理大量数据,这些数据在Pandas中需要占用大量RAM,以至于环境会崩溃。

我需要保存到Parquet,因为我正在处理numpy中的可变长度数组,所以对于这种情况,parquet实际上比.npy或.hdf5文件节省了更小的空间。

以下代码是一个最小的示例,它下载了我的一小部分数据,并在Pandas对象和Numpy对象之间进行转换,以测量它们消耗的RAM,并将其保存为npy和parquet文件,以查看它们占用多少磁盘空间。

# Download sample file, about 10 mbs

from sys import getsizeof
import requests
import pickle
import numpy as np
import pandas as pd
import os

def download_file_from_google_drive(id, destination):
    URL = "https://docs.google.com/uc?export=download"

    session = requests.Session()

    response = session.get(URL, params = { 'id' : id }, stream = True)
    token = get_confirm_token(response)

    if token:
        params = { 'id' : id, 'confirm' : token }
        response = session.get(URL, params = params, stream = True)

    save_response_content(response, destination)    

def get_confirm_token(response):
    for key, value in response.cookies.items():
        if key.startswith('download_warning'):
            return value

    return None

def save_response_content(response, destination):
    CHUNK_SIZE = 32768

    with open(destination, "wb") as f:
        for chunk in response.iter_content(CHUNK_SIZE):
            if chunk: # filter out keep-alive new chunks
                f.write(chunk)

download_file_from_google_drive('1-0R28Yhdrq2QWQ-4MXHIZUdZG2WZK2qR', 'sample.pkl')

sampleDF = pd.read_pickle('sample.pkl')

sampleDF.to_parquet( 'test1.pqt', compression = 'brotli', index = False )

# Parquet file takes up little space 
os.path.getsize('test1.pqt')

6594712

getsizeof(sampleDF)

22827172

sampleDF['totalCites2'] = sampleDF['totalCites2'].apply(lambda x: np.array(x))

#RAM reduced if the variable length batches are in numpy
getsizeof(sampleDF)

22401764

#Much less RAM as a numpy object 
sampleNumpy = sampleDF.values
getsizeof(sampleNumpy)

112

# Much more space in .npy form 
np.save( 'test2.npy', sampleNumpy) 
os.path.getsize('test2.npy')

20825382

# Numpy savez. Not as good as parquet 
np.savez_compressed( 'test3.npy', sampleNumpy )
os.path.getsize('test3.npy.npz')

9873964


2
那个112的数字毫无意义。通常情况下,sys.getsizeof不是衡量内存使用的好方法。 - hpaulj
有什么更好的方法来衡量内存使用? - SantoshGupta7
对于ndarraynbytes,它只是每个元素的大小(通常为4-8字节)乘以元素数量。一个DataFrame可能会将其数据存储在一个类似大小的数组中。但是如果您有数组的数组或列表(对象dtype),则必须考虑这些对象的大小。没有一个数字或衡量标准;您必须了解数据对象的结构。 - hpaulj
这个数组只有112,实际上与它派生自的数据框共享数据内存。112只是测量具有形状和步幅等属性的“对象”,并且不测量底层数据缓冲区大小(因为它不拥有它)。 - hpaulj
1
如果列中有许多重复的值,则 Pandas 稀疏数据结构可能会有所帮助 - 请参阅 https://pandas.pydata.org/pandas-docs/stable/user_guide/sparse.html 了解 pandas.SparseArray 和其他文档。 - Andrew Lavers
1
使用您的笔记本电脑,我使用 pickle.load 加载了 sample.pkl 文件。结果是一个 DataFrame。换句话说,鉴于数据源,您无法绕过 pandas。这是第二列中包含列表的版本。您的 apply 命令将它们转换为数组,但长度在 5 到 100 之间并没有太大区别。这是一个对象 dtype 列。 - hpaulj
2个回答

20
您可以直接使用Apache Arrow(pyarrow)读取/写入numpy数组到parquet中,这也是pandas中parquet的底层后端。请注意,parquet是一种表格格式,因此仍然需要创建一些表格。

您可以直接使用Apache Arrow(pyarrow)读取/写入numpy数组到parquet中,这也是pandas中parquet的底层后端。请注意,parquet是一种表格格式,因此仍然需要创建一些表格。

import numpy as np
import pyarrow as pa

np_arr = np.array([1.3, 4.22, -5], dtype=np.float32)
pa_table = pa.table({"data": np_arr})
pa.parquet.write_table(pa_table, "test.parquet")

参考: numpy 转 pyarrow, pyarrow.parquet.write_table


3

Parquet格式可以使用 pyarrow 来写入,正确的导入语法是:

import pyarrow.parquet as pq,这样就可以使用 pq.write_table。否则,使用 import pyarrow as pa, pa.parquet.write_table 将会返回: AttributeError: module 'pyarrow' has no attribute 'parquet'

Pyarrow要求数据按列组织,这意味着在 numpy 多维数组的情况下,您需要将每个维度分配给 parquet 列中的特定字段。

import numpy as np
import pyarrow as pa
import pyarrow.parquet as pq


ndarray = np.array(
    [
        [4.96266477e05, 4.55342071e06, -1.03240000e02, -3.70000000e01, 2.15592864e01],
        [4.96258372e05, 4.55344875e06, -1.03400000e02, -3.85000000e01, 2.40120775e01],
        [4.96249387e05, 4.55347732e06, -1.03330000e02, -3.47500000e01, 2.70718535e01],
    ]
)

ndarray_table = pa.table(
    {
        "X": ndarray[:, 0],
        "Y": ndarray[:, 1],
        "Z": ndarray[:, 2],
        "Amp": ndarray[:, 3],
        "Ang": ndarray[:, 4],
    }
)

pq.write_table(ndarray_table, "ndarray.parquet")

或者你可以直接使用 'import pyarrow.parquet' 和 'import pyarrow as pa' 的组合。 - bitbang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接