Типы данных pandas изменились при чтении из паркетного файла?

Я новичок в пандах и типе файлов паркета. У меня есть скрипт на Python, который:

  1. читает в паркетном файле hdfs
  2. преобразует его в фреймворк pandas
  3. перебирает определенные столбцы и изменяет некоторые значения
  4. записывает фрейм данных обратно в паркетный файл

Затем файл паркета импортируется обратно в hdfs с помощью impala-shell.

Проблема, с которой я столкнулся, связана с шагом 2. Я распечатываю содержимое фрейма данных сразу после его считывания и до того, как на шаге 3 будут внесены какие-либо изменения. Похоже, что меняются типы данных и данные некоторые поля, что вызывает проблемы при записи обратно в паркетный файл. Примеры:

  • поля, которые отображаются в базе данных как NULL, заменяются на string None (для строковых столбцов) или string nan (для числовых столбцов) в распечатке фрейма данных.
  • поля, которые должны быть Int со значением 0 в базе данных, изменяются на 0,00000 и превращаются в число с плавающей запятой в кадре данных.

Похоже, что он на самом деле меняет эти значения, потому что, когда он записывает файл паркета, а я импортирую его в hdfs и запускаю запрос, я получаю такие ошибки:

WARNINGS: File '<path>/test.parquet' has an incompatible Parquet schema for column 
'<database>.<table>.tport'. Column type: INT, Parquet schema:
optional double tport [i:1 d:1 r:0]

Я не знаю, почему это изменит данные, а не просто оставит их как есть. Если это то, что происходит, я не знаю, нужно ли мне перебирать каждый столбец и заменять все их исходными значениями, или есть другой способ сказать ему, чтобы он оставил их в покое.

Я использовал эту справочную страницу: http://arrow.apache.org/docs/python/parquet.html

Оно использует

pq.read_table(in_file) 

прочитать паркетный файл, а затем

df = table2.to_pandas()

для преобразования в фрейм данных, который я могу перебирать и изменять столбцы. Я не понимаю, почему он меняет данные, и не могу найти способ предотвратить это. Есть ли другой способ чтения, кроме read_table?

Если я запрошу базу данных, данные будут выглядеть так:

tport
0
1

Моя строка print (df) для того же самого выглядит так:

tport
0.00000
nan
nan
1.00000

Вот соответствующий код. Я пропустил часть, которая обрабатывает аргументы командной строки, так как она длинная и не относится к этой проблеме. Передан файл in_file:

import sys, getopt
import random
import re
import math

import pyarrow.parquet as pq
import numpy as np
import pandas as pd
import pyarrow as pa
import os.path

# <CLI PROCESSING SECTION HERE>

# GET LIST OF COLUMNS THAT MUST BE SCRAMBLED
field_file = open('scrambler_columns.txt', 'r') 
contents = field_file.read()
scrambler_columns = contents.split('\n')

def scramble_str(xstr):
    #print(xstr + '_scrambled!')
    return xstr + '_scrambled!'

parquet_file = pq.ParquetFile(in_file)
table2 = pq.read_table(in_file)
metadata = pq.read_metadata(in_file)

df = table2.to_pandas() #dataframe

print('rows: ' + str(df.shape[0]))
print('cols: ' + str(df.shape[1]))

pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
pd.set_option('display.width', None)
pd.set_option('display.max_colwidth', -1)
pd.set_option('display.float_format', lambda x: '%.5f' % x)

#df.fillna(value='', inplace=True) # np.nan # \xa0 

print(df) # print before making any changes


cols = list(df)
# https://pythonbasics.org/pandas-iterate-dataframe/
for col_name, col_data in df.iteritems():
    #print(cols[index])
    if col_name in scrambler_columns:
        print('scrambling values in column '  + col_name)

        for i, val in col_data.items():
            df.at[i, col_name] = scramble_str(str(val))

        
    
print(df) # print after making changes

print(parquet_file.num_row_groups)
print(parquet_file.read_row_group(0))

# WRITE NEW PARQUET FILE
new_table = pa.Table.from_pandas(df)
writer = pq.ParquetWriter(out_file, new_table.schema)
for i in range(1):
    writer.write_table(new_table)

writer.close()
    
if os.path.isfile(out_file) == True:
    print('wrote ' + out_file)
else:
    print('error writing file ' + out_file)

# READ NEW PARQUET FILE
table3 = pq.read_table(out_file)
df = table3.to_pandas() #dataframe
print(df)

EDIT Вот типы данных для первых нескольких столбцов в hdfs введите описание изображения здесь

и вот те же самые, что и в фрейме данных pandas:

id         object 
col1       float64
col2       object 
col3       object 
col4       float64
col5       object 
col6       object 
col7       object 

Кажется, чтобы преобразовать

String to object
Int    to float64
bigint to float64

Как я могу сказать пандам, какими типами данных должны быть столбцы?

Редактировать 2: мне удалось найти обходной путь, напрямую обработав таблицы Pyarrow. См. Мой вопрос и ответы здесь: Как обновить данные в pyarrow стол?


person raphael75    schedule 21.01.2021    source источник
comment
Есть ли в ваших int столбцах нули? Поддержка Pandas для int с нулевым значением является новой, и я считаю, что стрелка будет преобразовывать из int в float, если в столбце есть нули (так что nan доступен). Можете ли вы добавить вывод print(df.dtypes) и print(table2). Это покажет тип данных, который был в файле паркета, и типы данных, которые в конечном итоге использовали pandas.   -  person Pace    schedule 21.01.2021
comment
@Pace Я отредактировал исходный пост в конце, чтобы показать, что hdfs и pandas видят для типов данных.   -  person raphael75    schedule 21.01.2021
comment
Кроме того, как я могу обрабатывать нулевые / пустые значения? Мне нужно использовать df.fillna?   -  person raphael75    schedule 21.01.2021
comment
@Pace И да, столбцы int могут содержать нули.   -  person raphael75    schedule 21.01.2021


Ответы (1)


поля, которые отображаются как NULL в базе данных, заменяются строкой None (для строковых столбцов) или строкой nan (для числовых столбцов) в распечатке фрейма данных.

Это ожидаемо. Так определяется функция печати pandas.

Кажется, чтобы преобразовать String в объект

Это тоже ожидается. Numpy / pandas не имеет dtype для строк переменной длины. Можно использовать строковый тип фиксированной длины, но это было бы довольно необычно.

Похоже, чтобы преобразовать Int в float64

Это также ожидается, поскольку столбец имеет значения NULL, а int64 numpy не допускает значения NULL. Если вы хотите использовать целочисленный столбец Pandas, допускающий значение NULL, вы можете сделать ...

def lookup(t):
  if pa.types.is_integer(t):
    return pd.Int64Dtype()

df = table.to_pandas(types_mapper=lookup)

Конечно, вы можете создать более мелкозернистый поиск, если хотите использовать как Int32Dtype, так и Int64Dtype, это всего лишь шаблон для начала.

person Pace    schedule 21.01.2021
comment
Я попробовал это и получил эту ошибку: AttributeError: модуль 'pandas' не имеет атрибута 'Int64Dtype' - person raphael75; 21.01.2021
comment
А ... Я поймал старую версию своих попыток ... позвольте мне исправить это. - person Pace; 21.01.2021
comment
Подожди, забудь. Это правильно. Думаю, у вас более старая версия панд. Вы знаете, с какой версией работаете? Обнуляемые целые числа были добавлены в 0.24.0 - person Pace; 21.01.2021
comment
Похоже, у меня версия = '0.23.3'. Время обновляться. - person raphael75; 22.01.2021
comment
Обратите внимание, что специально для получения dtypes, допускающих значение NULL, в pandas вы также можете использовать функцию pandas read_parquet, которая имеет ключевое слово use_nullable_dtypes=True, которое будет использовать такой types_mapper для вас под капотом. - person joris; 25.01.2021