使用谓词从pyarrow.parquet.ParquetDataset筛选行

时间:2019-06-10 08:33:10

标签: python pandas amazon-s3 parquet pyarrow

我有一个存储在s3上的实木复合地板数据集,我想查询数据集中的特定行。我可以使用petastorm来做到这一点,但现在我只想使用pyarrow来做到这一点。

这是我的尝试:

import pyarrow.parquet as pq
import s3fs

fs = s3fs.S3FileSystem()

dataset = pq.ParquetDataset(
    'analytics.xxx', 
    filesystem=fs, 
    validate_schema=False, 
    filters=[('event_name', '=', 'SomeEvent')]
)

df = dataset.read_pandas().to_pandas()

但是这将返回pandas DataFrame,就像过滤器不起作用一样,也就是说,我的行具有各种值event_name。有什么我想念的东西或我误解了的东西吗?在获得pandas DataFrame之后,我可以进行过滤,但是我会使用比所需更多的内存空间。

4 个答案:

答案 0 :(得分:10)

对于从Google到达这里的任何人,您现在都可以在读取Parquet文件时过滤PyArrow中的行。不管您是通过pandas还是pyarrow.parquet阅读。

documentation

过滤器(列表[元组]或列表[列表[元组]]或无(默认))– 与过滤谓词不匹配的行将从扫描数据中删除。如果嵌套键结构中不包含匹配的行,则将利用这些键来避免完全加载文件。如果use_legacy_dataset为True,则过滤器只能引用分区键,并且仅支持配置单元样式的目录结构。将use_legacy_dataset设置为False时,还支持文件内过滤和不同的分区方案。

谓词以析取范式(DNF)表示,例如[[('x','=',0),...],...]。 DNF允许单列谓词的任意布尔逻辑组合。最里面的元组每个描述一个列谓词。内部谓词列表被解释为一个连词(AND),形成一个更具选择性和多列的谓词。最后,最外面的列表将这些过滤器组合为一个析取(OR)。

谓词也可以作为List [Tuple]传递。此形式被解释为单个连词。要在谓词中表达OR,必须使用(首选)List [List [Tuple]]表示法。

答案 1 :(得分:6)

注意:我已经在this post

中将其扩展为Python和Parquet的综合指南。

实木复合地板格式分区

要使用过滤器,您需要使用分区以Parquet格式存储数据。加载Parquet列中的几个和分区中的许多列,可以使Parquet和CSV大大提高I / O性能。 Parquet可以基于一个或多个字段的值对文件进行分区,并为嵌套值的唯一组合创建目录树,或者为一个分区列仅创建一组目录。 PySpark Parquet documentation解释了Parquet如何运作良好。

关于性别和国家的划分看起来像this

path
└── to
    └── table
        ├── gender=male
        │   ├── ...
        │   │
        │   ├── country=US
        │   │   └── data.parquet
        │   ├── country=CN
        │   │   └── data.parquet
        │   └── ...

如果需要进一步对数据进行分区,还可以进行行组分区,但是大多数工具仅支持指定行组大小,并且您必须自己进行key-->row group查找,这很丑陋(很乐意回答此问题另一个问题)。

用熊猫写分区

您需要使用Parquet对数据进行分区,然后才能使用过滤器加载数据。您可以使用PyArrow,pandas或用于大型数据集的DaskPySpark将数据写入分区。

例如,要在熊猫中写入分区:

df.to_parquet(
    path='analytics.xxx', 
    engine='pyarrow',
    compression='snappy',
    columns=['col1', 'col5'],
    partition_cols=['event_name', 'event_category']
)

这会将文件布置如下:

analytics.xxx/event_name=SomeEvent/event_category=SomeCategory/part-0001.c000.snappy.parquet
analytics.xxx/event_name=SomeEvent/event_category=OtherCategory/part-0001.c000.snappy.parquet
analytics.xxx/event_name=OtherEvent/event_category=SomeCategory/part-0001.c000.snappy.parquet
analytics.xxx/event_name=OtherEvent/event_category=OtherCategory/part-0001.c000.snappy.parquet

在PyArrow中加载镶木地板分区

要使用分区列通过一个属性来捕获事件,请在列表中放置一个元组过滤器:

import pyarrow.parquet as pq
import s3fs

fs = s3fs.S3FileSystem()

dataset = pq.ParquetDataset(
    's3://analytics.xxx', 
    filesystem=fs, 
    validate_schema=False, 
    filters=[('event_name', '=', 'SomeEvent')]
)
df = dataset.to_table(
    columns=['col1', 'col5']
).to_pandas()

使用逻辑AND进行过滤

要使用AND抓取具有两个或多个属性的事件,您只需创建一个过滤器元组列表:

import pyarrow.parquet as pq
import s3fs

fs = s3fs.S3FileSystem()

dataset = pq.ParquetDataset(
    's3://analytics.xxx', 
    filesystem=fs, 
    validate_schema=False, 
    filters=[
        ('event_name',     '=', 'SomeEvent'),
        ('event_category', '=', 'SomeCategory')
    ]
)
df = dataset.to_table(
    columns=['col1', 'col5']
).to_pandas()

使用逻辑OR进行过滤

要使用OR抓取两个事件,您需要将过滤器元组嵌套在它们自己的列表中:

import pyarrow.parquet as pq
import s3fs

fs = s3fs.S3FileSystem()

dataset = pq.ParquetDataset(
    's3://analytics.xxx', 
    filesystem=fs, 
    validate_schema=False, 
    filters=[
        [('event_name', '=', 'SomeEvent')],
        [('event_name', '=', 'OtherEvent')]
    ]
)
df = dataset.to_table(
    columns=['col1', 'col5']
).to_pandas()

使用AWS Data Wrangler加载木地板分区

作为另一个答案,将数据过滤仅加载到数据所在位置(本地或云中)中某些分区中某些列的最简单方法是使用awswrangler模块。如果您使用的是S3,请查看awswrangler.s3.read_parquet()awswrangler.s3.to_parquet()的文档。过滤的作用与上面的示例相同。

import awswrangler as wr

df = wr.s3.read_parquet(
    path="analytics.xxx",
    columns=["event_name"], 
    filters=[('event_name', '=', 'SomeEvent')]
)

使用pyarrow.parquet.read_table()加载镶木地板分区

如果您使用的是PyArrow,也可以使用pyarrow.parquet.read_table()

import pyarrow.parquet as pq

fp = pq.read_table(
    source='analytics.xxx',
    use_threads=True,
    columns=['some_event', 'some_category'],
    filters=[('event_name', '=', 'SomeEvent')]
)
df = fp.to_pandas()

使用PySpark加载实木复合地板分区

最后,您可以在PySpark中使用pyspark.sql.DataFrameReader.read_parquet()

import pyspark.sql.functions as F
from pyspark.sql import SparkSession

spark = SparkSession.builder.master("local[1]") \
                    .appName('Stack Overflow Example Parquet Column Load') \
                    .getOrCreate()

# I automagically employ Parquet structure to load the selected columns and partitions
df = spark.read.parquet('s3://analytics.xxx') \
          .select('event_name', 'event_category') \
          .filter(F.col('event_name') == 'SomeEvent')

希望这可以帮助您使用Parquet:)

答案 2 :(得分:3)

对于python 3.6 +,AWS有一个名为aws-data-wrangler的库,该库有助于Pandas / S3 / Parquet之间的集成,并允许您筛选分区的S3键。

安装do;

pip install awswrangler

要减少读取的数据,您可以根据存储在s3上的实木复合地板文件中的分区列来过滤行。 要使用值event_name过滤分区列"SomeEvent"中的行,请执行以下操作;

awswrangler <1.0.0

import awswrangler as wr

df = wr.pandas.read_parquet(
         path="s3://my-bucket/my/path/to/parquet-file.parquet",
         columns=["event_name"], 
         filters=[('event_name', '=', 'SomeEvent')]
)

awswrangler> 1.0.0做;

import awswrangler as wr

df = wr.s3.read_parquet(
         path="s3://my-bucket/my/path/to/parquet-file.parquet",
         columns=["event_name"], 
         filters=[('event_name', '=', 'SomeEvent')]
)

答案 3 :(得分:1)

当前,filters功能仅在文件级别实现,尚未在行级别实现。

因此,如果您有一个数据集作为嵌套层次结构中多个分区镶花文件的集合(此处描述的分区数据集的类型:official doc here),则可以使用filters参数仅读取文件的子集。
但是,您还不能用它来读取单个文件的行组的子集(请参见https://arrow.apache.org/docs/python/parquet.html#partitioned-datasets-multiple-files)。

但是,您会收到一条错误消息,指出了这样一个无效的过滤器,这是很好的。我为此打开了一个问题:https://issues.apache.org/jira/browse/ARROW-1796