it-swarm.com.de

Wie liest man eine Liste von Parkettdateien aus S3 als Pandas-Datenrahmen mit pyarrow?

Ich habe eine harte Art, dies mit boto3 (1.4.4), pyarrow (0.4.1) und pandas (0.20.3) zu erreichen.

Erstens kann ich eine einzelne Parkettdatei lokal wie folgt lesen:

import pyarrow.parquet as pq

path = 'parquet/part-r-00000-1e638be4-e31f-498a-a359-47d017a0059c.gz.parquet'
table = pq.read_table(path)
df = table.to_pandas()

Ich kann auch ein Verzeichnis der Parkettdateien lokal lesen:

import pyarrow.parquet as pq

dataset = pq.ParquetDataset('parquet/')
table = dataset.read()
df = table.to_pandas()

Beide wirken wie ein Zauber. Nun möchte ich dasselbe mit Dateien erreichen, die in einem S3-Bucket gespeichert sind. Ich hatte gehofft, dass so etwas funktionieren würde:

dataset = pq.ParquetDataset('s3n://dsn/to/my/bucket')

Aber es tut nicht:

OSError: Passed non-file path: s3n://dsn/to/my/bucket

Nach dem Lesen von pyarrows Dokumentation scheint dies gründlich nicht möglich im Moment . Also kam ich mit der folgenden Lösung heraus:

Lesen einer einzelnen Datei aus S3 und Abrufen eines Pandas-Datenrahmens:

import io
import boto3
import pyarrow.parquet as pq

buffer = io.BytesIO()
s3 = boto3.resource('s3')
s3_object = s3.Object('bucket-name', 'key/to/parquet/file.gz.parquet')
s3_object.download_fileobj(buffer)
table = pq.read_table(buffer)
df = table.to_pandas()

Und hier meine hackige, nicht so optimierte Lösung zum Erstellen eines Pandas-Datenrahmens aus einem S3-Ordnerpfad:

import io
import boto3
import pandas as pd
import pyarrow.parquet as pq

bucket_name = 'bucket-name'
def download_s3_parquet_file(s3, bucket, key):
    buffer = io.BytesIO()
    s3.Object(bucket, key).download_fileobj(buffer)
    return buffer

client = boto3.client('s3')
s3 = boto3.resource('s3')
objects_dict = client.list_objects_v2(Bucket=bucket_name, Prefix='my/folder/prefix')
s3_keys = [item['Key'] for item in objects_dict['Contents'] if item['Key'].endswith('.parquet')]
buffers = [download_s3_parquet_file(s3, bucket_name, key) for key in s3_keys]
dfs = [pq.read_table(buffer).to_pandas() for buffer in buffers]
df = pd.concat(dfs, ignore_index=True)

Gibt es einen besseren Weg, um dies zu erreichen? Vielleicht eine Art Anschluss für Pandas, die Pyarrow verwenden? Ich möchte die Verwendung von pyspark vermeiden, aber wenn es keine andere Lösung gibt, würde ich sie verwenden.

16

Sie sollten das Modul s3fs verwenden, wie von yjk21 vorgeschlagen. Als Ergebnis des Aufrufs von ParquetDataset erhalten Sie jedoch ein pyarrow.parquet.ParquetDataset-Objekt. Um den Pandas DataFrame zu erhalten, sollten Sie stattdessen .read_pandas().to_pandas() darauf anwenden:

import pyarrow.parquet as pq
import s3fs
s3 = s3fs.S3FileSystem()

pandas_dataframe = pq.ParquetDataset('s3://your-bucket/', filesystem=s3).read_pandas().to_pandas()
16
vak

Sie können s3fs von dask verwenden, das eine Dateisystemschnittstelle für S3 implementiert. Dann können Sie das Dateisystemargument von ParquetDataset wie folgt verwenden:

import s3fs
s3 = s3fs.S3FileSystem()
dataset = pq.ParquetDataset('s3n://dsn/to/my/bucket', filesystem=s3)
4
yjk21

Es kann auch mit boto3 ohne den Einsatz von pyarrow gemacht werden

import boto3
import io
import pandas as pd

# Read the parquet file
buffer = io.BytesIO()
s3 = boto3.resource('s3')
object = s3.Object('bucket_name','key')
object.download_fileobj(buffer)
df = pd.read_parquet(buffer)

print(df.head())
3
oya163

Die einfachste Möglichkeit, Parkettdaten aus der Cloud in Dataframes einzulesen, ist die Verwendung von dask.dataframe auf folgende Weise:

import dask.dataframe as dd
df = dd.read_parquet('s3://bucket/path/to/data-*.parq')

dask.dataframe kann aus Google Cloud Storage, Amazon S3, Hadoop-Dateisystem und mehr lesen

2
Rich Signell

Vielen Dank! Ihre Frage sagt mir eigentlich viel. So mache ich es jetzt mit pandas (0.21.1), das pyarrow und boto3 (1.3.1) aufruft.

import boto3
import io
import pandas as pd

# Read single parquet file from S3
def pd_read_s3_parquet(key, bucket, s3_client=None, **args):
    if s3_client is None:
        s3_client = boto3.client('s3')
    obj = s3_client.get_object(Bucket=bucket, Key=key)
    return pd.read_parquet(io.BytesIO(obj['Body'].read()), **args)

# Read multiple parquets from a folder on S3 generated by spark
def pd_read_s3_multiple_parquets(filepath, bucket, s3=None, 
                                 s3_client=None, verbose=False, **args):
    if not filepath.endswith('/'):
        filepath = filepath + '/'  # Add '/' to the end
    if s3_client is None:
        s3_client = boto3.client('s3')
    if s3 is None:
        s3 = boto3.resource('s3')
    s3_keys = [item.key for item in s3.Bucket(bucket).objects.filter(Prefix=filepath)
               if item.key.endswith('.parquet')]
    if not s3_keys:
        print('No parquet found in', bucket, filepath)
    Elif verbose:
        print('Load parquets:')
        for p in s3_keys: 
            print(p)
    dfs = [pd_read_s3_parquet(key, bucket=bucket, s3_client=s3_client, **args) 
           for key in s3_keys]
    return pd.concat(dfs, ignore_index=True)

Dann können Sie mehrere Parkette unter einem Ordner von S3 durch lesen

df = pd_read_s3_multiple_parquets('path/to/folder', 'my_bucket')

(Man kann diesen Code viel vereinfachen, denke ich.)

1
Louis Yang