Как читать секционированные паркетные файлы из S3 с использованием pyarrow в python

Я ищу способы чтения данных из нескольких секционированных каталогов из s3 с помощью python.

data_folder/время_в_числовом_формате = 1/cur_date = 20-12-2012/abcdsd0324324.snappy.parquet data_folder/время_в_числовом_формате = 2/cur_date = 27-12-2012/asdsdfsd0324324.snappy.parquet

pyarrow Модуль ParquetDataset имеет возможность читать из разделов. Поэтому я попробовал следующий код:

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

Выбросила следующую ошибку:

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/

Основываясь на документации pyarrow, я попытался использовать s3fs в качестве файловой системы, то есть:

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

Что вызывает следующую ошибку:

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'

Я ограничен использованием кластера ECS, поэтому искра /pyspark не вариант.

Есть ли способ легко прочитать файлы паркета легко, в python из таких секционированных каталогов в s3? Я считаю, что перечисление всех каталогов, а затем чтение не является хорошей практикой, как предлагается в этой ссылке. Мне нужно было бы преобразовать прочитанные данные в фреймворк pandas для дальнейшей обработки и, следовательно, отдать предпочтения параметрам, связанным с fastparquet или pyarrow. Я также открыт для других вариантов в python.

Ответ 1

Мне удалось заставить это работать с последней версией fastparquet & s3fs. Ниже приведен код для того же:

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()

кредиты Мартин за указание мне в правильном направлении через наш разговор

NB: Это будет медленнее, чем использование pyarrow, основываясь на тесте. Я обновлю свой ответ, как только поддержка s3fs будет реализована в pyarrow через ARROW-1213

Я сделал быстрый тест на индивидуальные итерации с помощью pyarrow и списка файлов, отправленных как fastparquet. fastparquet быстрее с s3fs против pyarrow + мой хакерский код. Но я считаю, что pyarrow +s3fs будет быстрее после его реализации.

Код и тесты ниже:

>>> 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.961556333000317

Обновление 2019

После всех PR были решены такие проблемы, как Arrow-2038 и Fast Parquet - PR # 182.

Чтение файлов паркета с помощью 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() 

Чтение файлов паркета с помощью 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()

Быстрые тесты

Вероятно, это не лучший способ для сравнения. пожалуйста, прочитайте сообщение в блоге для сквозного теста

#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.2677053569998407

#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.931876824000028

Дальнейшее чтение относительно скорости Pyarrow

Ссылка:

Ответ 3

Эта проблема была решена в этом запросе в 2017 году.

Для тех, кто хочет читать паркет из S3, используя только пиарроу, вот пример:

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()

Ответ 4

Для тех из вас, кто хочет читать только части разделенного файла паркета, pyarrow принимает список ключей, а также только частичный путь к каталогу для чтения во всех частях раздела. Этот метод особенно полезен для организаций, которые разбили свои наборы данных паркета на значимые значения, например, по годам или странам, позволяя пользователям указать, какие части файла им нужны. Это снизит затраты в долгосрочной перспективе, так как плата AWS за байт при чтении в наборах данных.

# Read in user specified partitions of a partitioned parquet file 

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

keys = ['keyname/blah_blah/part-00000-cc2c2113-3985-46ac-9b50-987e9463390e-c000.snappy.parquet'\
         ,'keyname/blah_blah/part-00001-cc2c2113-3985-46ac-9b50-987e9463390e-c000.snappy.parquet'\
         ,'keyname/blah_blah/part-00002-cc2c2113-3985-46ac-9b50-987e9463390e-c000.snappy.parquet'\
         ,'keyname/blah_blah/part-00003-cc2c2113-3985-46ac-9b50-987e9463390e-c000.snappy.parquet']

bucket = 'bucket_yada_yada_yada'

# Add s3 prefix and bucket name to all keys in list
parq_list=[]
for key in keys:
    parq_list.append('s3://'+bucket+'/'+key)

# Create your dataframe
df = pq.ParquetDataset(parq_list, filesystem=s3).read_pandas(columns=['Var1','Var2','Var3']).to_pandas()