Python 解码十进制类型的拼花地板最小/最大统计信息

Python 解码十进制类型的拼花地板最小/最大统计信息,python,parquet,pyarrow,Python,Parquet,Pyarrow,我使用pyarrow创建了一个具有十进制列类型pa.decimal128(12,4)的拼花地板文件。读取文件并访问其元数据后,我得到以下输出: <pyarrow._parquet.ColumnChunkMetaData object at 0x7f4752644310> file_offset: 26077 file_path: physical_type: FIXED_LEN_BYTE_ARRAY num_values: 3061 path_in_schema

我使用pyarrow创建了一个具有十进制列类型
pa.decimal128(12,4)
的拼花地板文件。读取文件并访问其元数据后,我得到以下输出:

<pyarrow._parquet.ColumnChunkMetaData object at 0x7f4752644310>
  file_offset: 26077
  file_path: 
  physical_type: FIXED_LEN_BYTE_ARRAY
  num_values: 3061
  path_in_schema: Price
  is_stats_set: True
  statistics:
    <pyarrow._parquet.Statistics object at 0x7f4752644360>
      has_min_max: True
      min: b'\x00\x00\x00\x00\x9b\xdc'
      max: b'\x00\x00w5\x93\x9c'
      null_count: 0
      distinct_count: 0
      num_values: 3061
      physical_type: FIXED_LEN_BYTE_ARRAY
      logical_type: Decimal(precision=12, scale=4)
      converted_type (legacy): DECIMAL
  compression: SNAPPY
  encodings: ('PLAIN_DICTIONARY', 'PLAIN', 'RLE')
  has_dictionary_page: True
  dictionary_page_offset: 22555
  data_page_offset: 23225
  total_compressed_size: 3522
  total_uncompressed_size: 3980
但却收到了以下错误消息

pyarrow.lib.ArrowNotImplementedError:不支持使用函数cast\u decimal将二进制转换为十进制


统计信息基于物理类型而不是逻辑类型。对于
Decimal(精度=12,刻度=4)
物理类型为
FIXED\u LEN\u BYTE\u ARRAY
,即最小值和最大值。不幸的是,为了转换回十进制,您需要知道Arrow是如何编码为固定长度字节数组的

它首先根据精度确定需要多少字节。您不需要对该零件进行反向工程。然后,它转换为big-endian,截断到所需的字节并写入它们。因此,这应该允许您转换回

def pad(b):
  # Left pad 0 or 1 based on leading digit (2's complement rules)
  if b[-1] & 128 == 0:
    return b.ljust(16, b'\x00')
  else:
    return b.ljust(16, b'\xff')

def to_pyarrow_bytes(b):
  # converts from big-endian (parquet's repr) to little endian (arrow's repr)
  # and then pads to 16 bytes
  return pad(b[::-1])

def decode_stats_decimal(b):
  pyarrow_bytes = to_pyarrow_bytes(b)
  arr = pa.Array.from_buffers(dtype, 1, [None, pa.py_buffer(pyarrow_bytes)], 0)
  return arr[0].as_py()

decode_stats_decimal(statistics.max)
# Decimal('199999.9900')
decode_stats_decimal(statistics.min)
# Decimal('3.9900')

对于小数128,您希望有16个字节,但这里只有6个字节。也许它们被截断了。在这种情况下,请尝试
int.from_bytes(b'\x00\x00\x00\x00\x00\x00\x9b\xdc',big')/1_000)
BTW,这里有一个用于将其解码为更好类型的方法。感谢您的解释。有时我可以看到解码的最小值/最大值与实际列最小值/最大值不同。在这个特定示例中,最小值为0.0,但统计结果为3.99。这似乎是错误的,我能够重现这一点。我在这里总结了错误的最小/最大值:这是编写十进制类型PARQUET-1655时已知的错误。现在应该在主分支上修复此问题,下一个版本(预计2021年4月发布的pyarrow 4.0)将包含此修复。
def pad(b):
  # Left pad 0 or 1 based on leading digit (2's complement rules)
  if b[-1] & 128 == 0:
    return b.ljust(16, b'\x00')
  else:
    return b.ljust(16, b'\xff')

def to_pyarrow_bytes(b):
  # converts from big-endian (parquet's repr) to little endian (arrow's repr)
  # and then pads to 16 bytes
  return pad(b[::-1])

def decode_stats_decimal(b):
  pyarrow_bytes = to_pyarrow_bytes(b)
  arr = pa.Array.from_buffers(dtype, 1, [None, pa.py_buffer(pyarrow_bytes)], 0)
  return arr[0].as_py()

decode_stats_decimal(statistics.max)
# Decimal('199999.9900')
decode_stats_decimal(statistics.min)
# Decimal('3.9900')