我正在寻找使用python从s3读取多个分区目录数据的方法。
data_folder / SERIAL_NUMBER = 1 / cur_date = 20-12-2012 / abcdsd0324324.snappy.parquet data_folder / SERIAL_NUMBER = 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群集,因此 spark / pyspark不是一个选项。
有没有一种方法可以轻松地从s3中的分区目录在python中轻松读取镶木地板文件?我觉得列出所有目录,然后阅读这不是一个好的做法,如link所示。我需要将读取数据转换为pandas数据帧以进行进一步处理。因此更喜欢与fastparquet或pyarrow相关的选项。我也对python中的其他选项持开放态度。
答案 0 :(得分:19)
对于python 3.6 +,AWS有一个名为aws-data-wrangler的库,可帮助实现Pandas / S3 / Parquet之间的集成
安装do;
pip install awswrangler
要使用awswrangler 1.x.x
及更高版本从s3中读取分区实木复合地板,请这样做;
import awswrangler as wr
df = wr.s3.read_parquet(path="s3://my_bucket/path/to/data_folder/", dataset=True)
通过设置dataset=True
,awswrangler需要分区的镶木地板文件。它将从您在path
中指定的s3键下面的分区中读取所有单独的镶木地板文件。
答案 1 :(得分:16)
我设法使用最新版本的fastparquet&amp; 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()
通过我们的conversation 向马丁指出我正确的方向
NB :根据benchmark,这比使用pyarrow要慢。一旦通过ARROW-1213
在pyarrow中实现s3fs支持,我将更新我的答案我使用pyarrow&amp; amp;进行了单独迭代的快速基准测试。文件列表以glob形式发送到fastparquet。使用s3fs vs pyarrow +我的hackish代码,fastparquet更快。但我认为pyarrow + s3fs一旦实现就会更快。
代码&amp;基准如下:
>>> 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&amp; 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()
使用快速拼花
读取镶木地板文件# 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()
快速基准
这可能不是对其进行基准测试的最佳方式。请阅读blog post了解基准
#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 speed
的进一步阅读参考:
答案 2 :(得分:2)
让我们在https://issues.apache.org/jira/browse/ARROW-1213和https://issues.apache.org/jira/browse/ARROW-1119进行讨论。我们必须添加一些代码以允许pyarrow识别s3fs文件系统并添加一个shim /兼容类,以使S3FS与pyarrow的文件系统API略有不同。
答案 3 :(得分:1)
此问题已在this pull request中于2017年得到解决。
对于那些只想使用pyarrow从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 :(得分:0)
对于只想读取分区木地板文件的部分的那些人,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()