web-dev-qa-db-fra.com

Comment lire des fichiers de parquet partitionnés de S3 en utilisant pyarrow en python

Je cherche des moyens de lire les données de plusieurs répertoires partitionnés à partir de s3 en utilisant python.

dossier_données/numéro_série = 1/cur_date = 20-12-2012/abcdsd0324324.snappy.parquet dossier_données/numéro de série = 2/cur_date = 27-12-2012/asdsdfsd0324324.snappy.parquet

le module ParquetDataset de pyarrow a la capacité de lire des partitions. J'ai donc essayé le code suivant: 

>>> import pandas as pd
>>> import pyarrow.parquet as pq
>>> import s3fs
>>> a = "s3://my_bucker/path/to/data_folder/"
>>> dataset = pq.ParquetDataset(a)

Il a jeté l'erreur suivante:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/my_username/anaconda3/lib/python3.6/site-packages/pyarrow/parquet.py", line 502, in __init__
    self.metadata_path) = _make_manifest(path_or_paths, self.fs)
  File "/home/my_username/anaconda3/lib/python3.6/site-packages/pyarrow/parquet.py", line 601, in _make_manifest
    .format(path))
OSError: Passed non-file path: s3://my_bucker/path/to/data_folder/

Sur la base de la documentation de pyarrow, j’ai essayé d’utiliser s3fs comme système de fichiers, c’est-à-dire:

>>> dataset = pq.ParquetDataset(a,filesystem=s3fs)

Ce qui lève l'erreur suivante: 

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/my_username/anaconda3/lib/python3.6/site-packages/pyarrow/parquet.py", line 502, in __init__
    self.metadata_path) = _make_manifest(path_or_paths, self.fs)
  File "/home/my_username/anaconda3/lib/python3.6/site-packages/pyarrow/parquet.py", line 583, in _make_manifest
    if is_string(path_or_paths) and fs.isdir(path_or_paths):
AttributeError: module 's3fs' has no attribute 'isdir'

Je suis limité à utiliser un cluster ECS, donc spark/pyspark n'est pas une option.

Existe-t-il un moyen de lire facilement les fichiers de parquet, en python à partir de tels répertoires partitionnés dans s3? Je pense que lister tous les répertoires puis lire le n'est pas une bonne pratique comme suggéré dans ce link . J'aurais besoin de convertir les données lues en un fichier de données de pandas pour un traitement ultérieur, et donc de préférer les options liées à fastparquet ou à pyarrow. Je suis également ouvert à d’autres options en python.

12
stormfield

J'ai réussi à le faire fonctionner avec la dernière version de fastparquet & s3fs. Ci-dessous le code pour le même:

import s3fs
import fastparquet as fp
s3 = s3fs.S3FileSystem()
fs = s3fs.core.S3FileSystem()

#mybucket/data_folder/serial_number=1/cur_date=20-12-2012/abcdsd0324324.snappy.parquet 
s3_path = "mybucket/data_folder/*/*/*.parquet"
all_paths_from_s3 = fs.glob(path=s3_path)

myopen = s3.open
#use s3fs as the filesystem
fp_obj = fp.ParquetFile(all_paths_from_s3,open_with=myopen)
#convert to pandas dataframe
df = fp_obj.to_pandas()

crédits à martin pour m'avoir orienté dans la bonne direction via notre conversation

NB: Cela serait plus lent que pyarrow, basé sur benchmark . Je mettrai à jour ma réponse une fois que le support s3fs sera implémenté dans pyarrow via ARROW-1213

J'ai fait un rapide test sur différentes itérations individuelles avec pyarrow et la liste des fichiers envoyés en tant que glob à fastparquet. FastParquet est plus rapide avec S3FS vs pyarrow + mon code hackish. Mais je pense que pyarrow + s3fs sera plus rapide une fois implémenté.

Le code et les repères sont ci-dessous:

>>> def test_pq():
...     for current_file in list_parquet_files:
...         f = fs.open(current_file)
...         df = pq.read_table(f).to_pandas()
...         # following code is to extract the serial_number & cur_date values so that we can add them to the dataframe
...         #probably not the best way to split :)
...         elements_list=current_file.split('/')
...         for item in elements_list:
...             if item.find(date_partition) != -1:
...                 current_date = item.split('=')[1]
...             Elif item.find(dma_partition) != -1:
...                 current_dma = item.split('=')[1]
...         df['serial_number'] = current_dma
...         df['cur_date'] = current_date
...         list_.append(df)
...     frame = pd.concat(list_)
...
>>> timeit.timeit('test_pq()',number =10,globals=globals())
12.078817503992468

>>> def test_fp():
...     fp_obj = fp.ParquetFile(all_paths_from_s3,open_with=myopen)
...     df = fp_obj.to_pandas()

>>> timeit.timeit('test_fp',number =10,globals=globals())
2.0100269466638565e-06

Mise à jour 2019

Après tous les PR, des problèmes tels que Arrow-2038 & Fast Parquet - PR # 182 ont été résolus.

Lire les fichiers de parquet avec Pyarrow

# pip install pyarrow
# pip install s3fs

>>> import s3fs
>>> import pyarrow.parquet as pq
>>> fs = s3fs.S3FileSystem()

>>> bucket = 'your-bucket-name'
>>> path = 'directory_name' #if its a directory omit the traling /
>>> bucket_uri = f's3://{bucket}/{path}'
's3://your-bucket-name/directory_name'

>>> dataset = pq.ParquetDataset(bucket_uri, filesystem=fs)
>>> table = dataset.read()
>>> df = table.to_pandas() 

Lecture des limes de parquet avec Fast parquet

# pip install s3fs
# pip install fastparquet

>>> import s3fs
>>> import fastparquet as fp

>>> bucket = 'your-bucket-name'
>>> path = 'directory_name'
>>> root_dir_path = f'{bucket}/{path}'
# the first two wild card represents the 1st,2nd column partitions columns of your data & so forth
>>> s3_path = f"{root_dir_path}/*/*/*.parquet"
>>> all_paths_from_s3 = fs.glob(path=s3_path)

>>> fp_obj = fp.ParquetFile(all_paths_from_s3,open_with=myopen, root=root_dir_path)
>>> df = fp_obj.to_pandas()

Repères rapides

Ce n'est probablement pas le meilleur moyen de le comparer. s'il vous plaît lire le blog post pour un point de repère à travers

#pyarrow
>>> import timeit
>>> def test_pq():
...     dataset = pq.ParquetDataset(bucket_uri, filesystem=fs)
...     table = dataset.read()
...     df = table.to_pandas()
...
>>> timeit.timeit('test_pq()',number =10,globals=globals())
1.8501588020008057

#fastparquet
>>> def test_fp():
...     fp_obj = fp.ParquetFile(all_paths_from_s3,open_with=myopen, root=root_dir_path)
...     df = fp_obj.to_pandas()

>>> timeit.timeit('test_fp',number =10,globals=globals())
2.0599982235580683e-06

Lectures supplémentaires sur Pyarrow's speed

Référence :

9
stormfield

Discutons dans https://issues.Apache.org/jira/browse/ARROW-1213 et https://issues.Apache.org/jira/browse/ARROW-1119 . Nous devons ajouter du code pour permettre à pyarrow de reconnaître le système de fichiers s3fs et d’ajouter une classe shim/compatibilité afin de conformer l’API du système de fichiers légèrement différent de S3FS à celui de pyarrow.

2
Wes McKinney

Ce problème a été résolu dans cette demande d'extraction en 2017.

Pour ceux qui veulent lire du parquet de S3 en utilisant seulement pyarrow, voici un exemple:

import s3fs
import pyarrow.parquet as pq
from pyarrow.filesystem import S3FSWrapper

fs = s3fs.S3FileSystem()
bucket = "your-bucket"
path = "your-path"

# Python 3.6 or later
p_dataset = pq.ParquetDataset(
    f"s3://{bucket}/{path}",
    filesystem=fs
)
df = p_dataset.read().to_pandas()

# Pre-python 3.6
p_dataset = pq.ParquetDataset(
    "s3://{0}/{1}".format(bucket, path),
    filesystem=fs
)
df = p_dataset.read().to_pandas()
1
efbbrown