Считываем числовые данные из файла на Python

В данной статье речь пойдет о простой на первой взгляд задаче — считывании числовых данных из текстовых файлов на Python. В сети можно найти десятки способов решения этой задачи, однако эти алгоритмы оказываются малоэффективными при работе с большим объемом данных. В данной статье будут разобраны самые популярные методики, а также произведено сравнение их скорости работы.

Введение

Когда я только начинал изучать Python, главным помощником в работе для меня, как наверное и для большинства программистов, был Stack Overflow. Я почерпнул оттуда много полезной информации, в том числе и о работе с файлами. Однако даже такая тривиальная задача, как оказалось, имеет несколько различных решений, отличающихся друг от друга простотой реализации и скоростью работы.

Большинство предложенных методов предполагают чтение файла построчно с дальнейшим разбиением на блоки и их преобразованием из строкового типа в числовой, поскольку Python в отличии от C/C++ работает с файлами как с массивом строк. Выполнить последовательное чтение данных в массив без преобразования типов, как это можно сделать в C/C++, стандартными средствами языка невозможно (насколько мне известно), и это существенно увеличивает время работы программы при обработке больших объемов данных.

Способы чтения данных из файла

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

  • построчное считывание с разбиением и преобразованием типов
  • использование библиотек, которые средствами других языков (например, C/C++) считывают файл и передают полученные данные интерпретатору Python

Ниже представлена подборка самых популярных методов чтения числовых данных на Python, отмеченных сообществом Stack Overflow как «best answer».

Способ 1 — построчное считывание с преобразованием

Самый популярный и простой вариант. Заключается в построчном чтении с разбиением полученной строки на блоки, которые затем преобразуются к необходимому типу данных (в данном случае float) и добавляются к заранее созданному списку.

data = []
with open("data.txt") as f:
    for line in f:
        data.append([float(x) for x in line.split()])

Способ 2 — преобразование при помощи map

Способ аналогичен предыдущему, за исключением того, что преобразованием данных из строкового формата в числовой занимается функция map.

file = open("data.txt", "r")
data = [map(float, line.split("\t")) for line in file]

Способ 3 — с использованием регулярного выражения

Данный способ можно назвать стрельбой из пушки по воробьям, однако у него все же есть свои плюсы: если данные в файле расположены хаотично и отсутствует постоянная структура, то функции split невозможно задать конкретный разделитель и для решения задачи можно использовать регулярное выражение, которое найдет в строке все числа, несмотря на их расположение и наличие разделителей.

import re

file = open("data.txt")
values = file.read().split("\n")
data = []

for key in values:
    value = re.findall(r"[-+]?\d*\.\d+|\d+", key)

    if value != []:
        data.append(value)

Способ 4 — с использованием CSV Reader

Если данные записаны в виде матрицы с постоянными разделителями, то выполнить их чтение можно при помощи модуля CSV Reader, указав в качестве параметра значение разделителя.

import csv

with open("data.txt") as f:
    data = [map(float, row) for row in csv.reader(f, delimiter='\t')]

Способ 5 — Numpy loadtxt

Библиотека Numpy предоставляет широкий набор модулей и функций для обработки числовых данных, в том числе и для чтения массивов из файлов. Одна из реализаций возможна с помощью функции loadtxt, результат работы которой будет записан в numpy.array.

import numpy as np

data = np.loadtxt("data.txt", delimiter='\t', dtype=np.float)

Способ 6 — Numpy genfromtxt

Данный способ не сильно отличается от предыдущего, за исключением того, что genfromtxt предоставляет более широкий набор входных параметров: указание различных типов данных для каждого из столбцов, передача ключей для создания ассоциативного массива и так далее.

import numpy as np

data = np.genfromtxt("data.txt", delimiter='\t', dtype=np.float)

Способ 7 — Pandas read_csv

Pandas — мощная библиотека для обработки данных на Python. В данном примере рассматривается только чтение данных, но её возможности этим не ограничены. Метод read_csv предоставляет широкий набор входных параметров, а также показывается высокую скорость работы даже при работе с большими объемами данных.

import pandas as pd

data = pd.read_csv("data.txt", sep="\t", header=None)

Методы тестирования скорости чтения

Для тестирования скорости чтения числовых данных были сгенерированы 7 тестовых файлов, содержащих 5 столбцов и 10, 100, 1 000, 10 000, 100 000, 1 000 000 и 10 000 000 строк случайных чисел формата float. Размер самого большого файла составил 742 Мб.

Для измерения времени работы программы использовалась функция time. Существует мнение, что измерять с её помощью время работы некорректно. Однако в данном случае меня интересовало работа с большими объемами данных, когда время работы программы составляло несколько десятков секунд. В таком случае отклонение в полсекунды вносило погрешность менее 1%.

Сравнение с компилируемыми языками программирования

Программы, созданные на компилируемых языках программирования, работают быстрее, чем их аналоги, написанные на интерпретируемых языках. Мне было интересно сравнить скорость чтения каждого метода с Fortran и C++ — самыми популярными языками в научном программировании, с которыми мне также приходится иметь дело в силу специфики моей работы.

Fortran

Несмотря на то, что Fortran считается устаревшим языком, он все еще очень популярен в научном программировании благодаря простоте написания кода, скорости обмена данных и обширном количестве библиотек, созданных за последние полвека.

Например, считать числовую матрицу из файла можно всего за 3 строчки кода при условии корректности входных данных.

real, dimension (5, 1000) :: data
open (1, file='data.txt')
read(1, *) data

C++

Дискуссии о том, что лучше: Fortran или C++ ведутся уже давно, даже среди авторов EasyCoding этот спор возникал несколько раз, поэтому мне было еще интересней протестировать чтение матриц на данном языке.

ifstream file("data.txt");

int count = 100000;
float** data = new float*[count];

for(int i = 0; i < count; i++)
{
	data[i] = new float[5];

	for(int j = 0; j < 5; j++) 
		file << data[i][j];
}

Результаты тестирования

В ходе эксперимента были протестированы 7 программ на языке Python и по одной на Fortran и C++, код которых представлен выше. Запуск программ осуществлялся на компьютере с Intel Core i5 2.7 GHz и 8 Гб оперативной памяти.

Для запуска программ использовались следующие интерпретаторы и компиляторы:

  • Python 3.5.2

  • GNU Fortran (GCC) 6.1.0

  • g++ 4.2.1

Для каждой программы проводилась серия испытаний и измерялось время работы, после чего записывался результат в виде среднего арифметического полученных данных. В таблице ниже жирным в каждой строке выделено наименьшее время работы в зависимости от способа чтения и размера входного файла.

Число строк Способ
1 2 3 4 5 6 7 Fortran C++
10 0.048 0.048 0.045 0.044 0.173 0.216 0.479 0.005 0.005
100 0.053 0.052 0.05 0.048 0.185 0.223 0.511 0.007 0.006
1 000 0.056 0.053 0.053 0.052 0.187 0.233 0.6 0.01 0.01
10 000 0.085 0.076 0.096 0.083 0.305 0.292 0.636 0.032 0.041
100 000 0.414 0.403 0.561 0.482 1.537 0.874 0.796 0.244 0.363
1 000 000 3.835 4.502 6.086 5.276 13.607 6.754 1.763 2.584 3.662
10 000 000 47.931 156.944 137.398 144.75 162.724 85.642 13.632 25.652 36.622

Итог

В ходе данного исследования были протестированы 7 самых популярных варианта чтения числовых матриц на языке Python, предложенными пользователями сайта Stack Overflow и отмеченными сообществом как «верный ответ». Как видно из таблицы с результатами, скорость работы программ не сильно отличается при использовании способов 1-4 на небольших объемах данных. Это связано с тем, что интерпретатор не тратит время на инициализацию сторонней библиотеки, как в методах 5-7.

Однако при увеличении объема входных данных лучше всех себя показал метод 7 с использованием библиотеки Pandas, который даже обогнал по скорости чтения данных языки C++ и Fortran.

Также из результатов теста можно видеть, что программа на  Fortran справилась с чтением данных быстрей аналога на C++, что еще раз доказывает его превосходство над самым популярным языком программирования в мире.

10 commentaries to post

  1. Наконец, нашел, что искал. Способ 6 — Numpy genfromtxt, который предоставляет более широкий набор входных параметров: указание различных типов данных для каждого из столбцов, передача ключей для создания ассоциативного массива и так далее.
    Спасибо. Сэкономили время на поиск единственного, что нужно для моих вычислений по таблице «тексты-слова»…

  2. ошибка в таблице ! 0.044 не меньше чем 0.005 а больше почти в 9 раз!

    1. Нет ошибки. Автор сравнивал скорости Python решений.

  3. С++, который обгонит всё перечисленное:

    FILE* f = fopen("data.txt",  "rb")
    int count = 10000;
    float** data = new float*[count];
    for(int i = 0; i &lt; count; ++i)
    {
      data[i] = new float[5];
      fread(data[i], sizeof(float) * 5, f);
    }
    

    Причем не особо кошерная реализация. Но соответствует предоставленному коду.
    Кошерная реализация это:

    fread(data, sizeof(float) * 5 * count, f);
  4. Спасибо за приведенное полезное сравнение. Не хватает сравнения скорости записи (в рам для точности)

  5. К способу 6:
    ModuleNotFoundError: No module named ‘numpy’
    Ругается на import в начале

    1. Необходимо установить соответствующую библиотеку numpy либо из репозиториев, либо посредством pip.

  6. 2 способ выдает:
    [/, /, /]
    при числах в файле:
    1 2 3
    4 5 6
    7 8 9
    Что не так?

    1. Проверьте версию интерпретатора Python. В 3.7 работает нормально.

  7. На С++ вы читали потоками, это медленно. Надо было попробовать функциями ввода вывода,fopen, fclose, fread должно быть быстрее. В С++ тоже несколько способов. Могло получиться сопоставимо с лучшим результатом.

Обсуждение закрыто.