Эффективное чтение данных из CSV в фрейм данных с несколькими разделителями - PullRequest
0 голосов
/ 04 января 2019

У меня есть неуклюжий CSV-файл с несколькими разделителями: разделитель для нечисловой части - ',', для числовой - ';'. Я хочу построить кадр данных только из числовой части настолько эффективно, насколько это возможно.

Я сделал 5 попыток: среди них, используя аргумент converters pd.read_csv, используя регулярное выражение с engine='python', используя str.replace. Все они более чем в 2 раза медленнее, чем чтение всего файла CSV без преобразований. Это слишком медленно для моего варианта использования.

Я понимаю, что сравнение не является чем-то похожим, но оно демонстрирует общую низкую производительность , а не , обусловленную вводом / выводом. Есть ли более эффективный способ считывания данных в числовой кадр данных Pandas? Или эквивалентный массив NumPy?

Приведенная ниже строка может использоваться для целей тестирования.

# Python 3.7.0, Pandas 0.23.4

from io import StringIO
import pandas as pd
import csv

# strings in first 3 columns are of arbitrary length
x = '''ABCD,EFGH,IJKL,34.23;562.45;213.5432
MNOP,QRST,UVWX,56.23;63.45;625.234
'''*10**6

def csv_reader_1(x):
    df = pd.read_csv(x, usecols=[3], header=None, delimiter=',',
                     converters={3: lambda x: x.split(';')})
    return df.join(pd.DataFrame(df.pop(3).values.tolist(), dtype=float))

def csv_reader_2(x):
    df = pd.read_csv(x, header=None, delimiter=';',
                     converters={0: lambda x: x.rsplit(',')[-1]}, dtype=float)
    return df.astype(float)

def csv_reader_3(x):
    return pd.read_csv(x, usecols=[3, 4, 5], header=None, sep=',|;', engine='python')

def csv_reader_4(x):
    with x as fin:
        reader = csv.reader(fin, delimiter=',')
        L = [i[-1].split(';') for i in reader]
        return pd.DataFrame(L, dtype=float)

def csv_reader_5(x):
    with x as fin:
        return pd.read_csv(StringIO(fin.getvalue().replace(';',',')),
                           sep=',', header=None, usecols=[3, 4, 5])

Проверка:

res1 = csv_reader_1(StringIO(x))
res2 = csv_reader_2(StringIO(x))
res3 = csv_reader_3(StringIO(x))
res4 = csv_reader_4(StringIO(x))
res5 = csv_reader_5(StringIO(x))

print(res1.head(3))
#        0       1         2
# 0  34.23  562.45  213.5432
# 1  56.23   63.45  625.2340
# 2  34.23  562.45  213.5432

assert all(np.array_equal(res1.values, i.values) for i in (res2, res3, res4, res5))

Результаты бенчмаркинга:

%timeit csv_reader_1(StringIO(x))  # 5.31 s per loop
%timeit csv_reader_2(StringIO(x))  # 6.69 s per loop
%timeit csv_reader_3(StringIO(x))  # 18.6 s per loop
%timeit csv_reader_4(StringIO(x))  # 5.68 s per loop
%timeit csv_reader_5(StringIO(x))  # 7.01 s per loop
%timeit pd.read_csv(StringIO(x))   # 1.65 s per loop

Обновление

Я открыт для использования инструментов командной строки в качестве крайней меры. В той степени, я включил такой ответ. Я надеюсь, что есть решение на чистом Python или Pandas с сопоставимой эффективностью.

Ответы [ 6 ]

0 голосов
/ 14 января 2019

Python имеет мощные функции для манипулирования данными, но не ожидайте, что производительность с использованием python. Когда требуется производительность, C и C ++ - ваш друг. Любая быстрая библиотека на python написана на C / C ++. Использовать код C / C ++ в python довольно просто, взгляните на утилиту swig (http://www.swig.org/tutorial.html)). Вы можете написать класс c ++, который может содержать некоторые быстрые утилиты, которые вы будете использовать в коде python при необходимости.

0 голосов
/ 14 января 2019

В моей среде (Ubuntu 16.04, 4 ГБ ОЗУ, Python 3.5.2) самый быстрый метод был (прототип 1 ) csv_reader_5 (взято из U9-Forward ) который работал только менее чем на 25% медленнее, чем чтение всего файла CSV без преобразований. Я улучшил этот подход, реализовав фильтр / оболочку, которая заменяет символ в методе read():

class SingleCharReplacingFilter:

    def __init__(self, reader, oldchar, newchar):
        def proxy(obj, attr):
            a = getattr(obj, attr)
            if attr in ('read'):
                def f(*args):
                    return a(*args).replace(oldchar, newchar)
                return f
            else:
                return a

        for a in dir(reader):
            if not a.startswith("_") or a == '__iter__':
                setattr(self, a, proxy(reader, a))

def csv_reader_6(x):
    with x as fin:
        return pd.read_csv(SingleCharReplacingFilter(fin, ";", ","),
                            sep=',', header=None, usecols=[3, 4, 5])

Результат - немного лучшая производительность по сравнению с чтением всего файла CSV без преобразований:

In [3]: %timeit pd.read_csv(StringIO(x))
605 ms ± 3.24 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [4]: %timeit csv_reader_5(StringIO(x))
733 ms ± 3.49 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [5]: %timeit csv_reader_6(StringIO(x))
568 ms ± 2.98 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

1 Я называю это прототипом, поскольку предполагается, что входной поток имеет тип StringIO (поскольку он вызывает .getvalue() для него).

0 голосов
/ 13 января 2019

Очень, очень, очень быстрый, 3.51 - результат, просто сделайте csv_reader_4 ниже, он просто конвертирует StringIO в str, затем заменяет ; на , и читает фрейм данных с sep=',':

def csv_reader_4(x):
    with x as fin:
        reader = pd.read_csv(StringIO(fin.getvalue().replace(';',',')), sep=',',header=None)
    return reader

Тест:

%timeit csv_reader_4(StringIO(x)) # 3.51 s per loop
0 голосов
/ 10 января 2019

Как насчет использования генератора для замены и объединения его с соответствующим декоратором, чтобы получить файлоподобный объект, подходящий для панд?

import io
import pandas as pd

# strings in first 3 columns are of arbitrary length
x = '''ABCD,EFGH,IJKL,34.23;562.45;213.5432
MNOP,QRST,UVWX,56.23;63.45;625.234
'''*10**6

def iterstream(iterable, buffer_size=io.DEFAULT_BUFFER_SIZE):
    """
    http://stackoverflow.com/a/20260030/190597 (Mechanical snail)
    Lets you use an iterable (e.g. a generator) that yields bytestrings as a
    read-only input stream.

    The stream implements Python 3's newer I/O API (available in Python 2's io
    module).

    For efficiency, the stream is buffered.
    """
    class IterStream(io.RawIOBase):
        def __init__(self):
            self.leftover = None
        def readable(self):
            return True
        def readinto(self, b):
            try:
                l = len(b)  # We're supposed to return at most this much
                chunk = self.leftover or next(iterable)
                output, self.leftover = chunk[:l], chunk[l:]
                b[:len(output)] = output
                return len(output)
            except StopIteration:
                return 0    # indicate EOF
    return io.BufferedReader(IterStream(), buffer_size=buffer_size)

def replacementgenerator(haystack, needle, replace):
    for s in haystack:
        if s == needle:
            yield str.encode(replace);
        else:
            yield str.encode(s);

csv = pd.read_csv(iterstream(replacementgenerator(x, ";", ",")), usecols=[3, 4, 5])

Обратите внимание, что мы преобразовываем строку (или составляющие ее символы) в байты через str.encode, поскольку это требуется для использования Pandas.

Этот подход функционально идентичен ответу Даниэле, за исключением того факта, что мы заменяем значения «на лету», так как они запрашиваются вместо всех сразу.

0 голосов
/ 07 января 2019

Используйте инструмент командной строки

На сегодняшний день наиболее эффективным решением, которое я нашел, является использование специального инструмента командной строки для замены ";" на "," и , а затем для чтения в Pandas. Решения Pandas или чистого Python не приближаются по эффективности.

По сути, использование CPython или инструмента, написанного на C / C ++, может превзойти манипуляции на уровне Python.

Например, используя Найти и заменить текст :

import os

os.chdir(r'C:\temp')                       # change directory location
os.system('fart.exe -c file.csv ";" ","')  # run FART with character to replace

df = pd.read_csv('file.csv', usecols=[3, 4, 5], header=None)  # read file into Pandas
0 голосов
/ 07 января 2019

Если это опция, замена символа ; на , в строке выполняется быстрее. Я записал строку x в файл test.dat.

def csv_reader_4(x):
    with open(x, 'r') as f:
        a = f.read()
    return pd.read_csv(StringIO(unicode(a.replace(';', ','))), usecols=[3, 4, 5])

Функция unicode() была необходима, чтобы избежать ошибки TypeError в Python 2.

Бенчмаркинг:

%timeit csv_reader_2('test.dat')  # 1.6 s per loop
%timeit csv_reader_4('test.dat')  # 1.2 s per loop
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...