Импорт данных с помощью genfromtxt
NumPy предоставляет несколько функций для создания массивов из табличных данных. Здесь мы сосредоточимся на функции genfromtxt.
Вкратце, genfromtxt выполняет два основных цикла. Первый цикл преобразует каждую строку файла в последовательность строк. Второй цикл преобразует каждую строку в соответствующий тип данных. Этот механизм медленнее, чем однократный цикл, но обеспечивает большую гибкость. В частности, genfromtxt может учитывать пропущенные данные, в то время как другие более быстрые и простые функции, такие как loadtxt, не могут.
Примечание
При приведении примеров мы будем использовать следующие соглашения:
>>> import numpy as np >>> from io import StringIO
Определение входных данных
Единственным обязательным аргументом функции genfromtxt является источник данных. Он может быть строкой, списком строк, генератором или открытым объектом типа файла с методом read , например, файл или объект io.StringIO. Если предоставлена одна строка, предполагается, что это имя локального или удаленного файла. Если предоставлен список строк или генератор, возвращающий строки, каждая строка рассматривается как одна строка в файле. При передаче URL удаленного файла файл автоматически загружается в текущий каталог и открывается.
Распознаваемые типы файлов — текстовые файлы и архивы. В настоящее время функция распознает gzip и bz2 (bzip2) архивы. Тип архива определяется по расширению файла: если имя файла заканчивается на '.gz', ожидается архив gzip; если оно заканчивается на 'bz2', предполагается архив bzip2.
Разделение строк на столбцы
Аргумент delimiter
После определения файла и его открытия для чтения функция genfromtxt разделяет каждую ненулевую строку на последовательность строк. Пустые или комментированные строки просто пропускаются. Ключевое слово delimiter используется для определения способа разделения.
Довольно часто для разделения столбцов используется один символ. Например, в файлах с запятыми (CSV) используются запятая (,) или точка с запятой (;) в качестве разделителя:
>>> data = "1, 2, 3\n4, 5, 6"
>>> np.genfromtxt(StringIO(data), delimiter=",")
array([[1., 2., 3.],
[4., 5., 6.]])
Другой распространенный разделитель — "\t", символ табуляции. Однако мы не ограничены одним символом, любой строкой может быть. По умолчанию функция genfromtxt предполагает delimiter=None, что означает, что строка разделяется по пробелам (включая табуляции), а последовательные пробелы считаются одним пробелом.
В качестве альтернативы, мы можем работать с файлом с фиксированной шириной, где столбцы определяются заданным числом символов. В этом случае нужно задать delimiter на одно целое число (если все столбцы имеют одинаковый размер) или на последовательность целых чисел (если столбцы могут иметь разный размер):
>>> data = " 1 2 3\n 4 5 67\n890123 4"
>>> np.genfromtxt(StringIO(data), delimiter=3)
array([[ 1., 2., 3.],
[ 4., 5., 67.],
[890., 123., 4.]])
>>> data = "123456789\n 4 7 9\n 4567 9"
>>> np.genfromtxt(StringIO(data), delimiter=(4, 3, 2))
array([[1234., 567., 89.],
[ 4., 7., 9.],
[ 4., 567., 9.]])
Аргумент autostrip
По умолчанию, когда строка разлагается на серию строк, отдельные элементы не очищаются от начальных и конечных пробелов. Это поведение может быть изменено путем задания необязательного аргумента autostrip со значением True:
>>> data = "1, abc , 2\n 3, xxx, 4"
>>> # Without autostrip
>>> np.genfromtxt(StringIO(data), delimiter=",", dtype="|U5")
array([['1', ' abc ', ' 2'],
['3', ' xxx', ' 4']], dtype='<U5')
>>> # With autostrip
>>> np.genfromtxt(StringIO(data), delimiter=",", dtype="|U5", autostrip=True)
array([['1', 'abc', '2'],
['3', 'xxx', '4']], dtype='<U5')
Аргумент comments
Необязательный аргумент comments используется для определения строки символов, отмечающей начало комментария. По умолчанию genfromtxt предполагает comments='#'. Маркер комментария может встречаться в любой части строки. Любой символ, присутствующий после маркера(ов) комментария, просто игнорируется:
>>> data = """#
... # Skip me !
... # Skip me too !
... 1, 2
... 3, 4
... 5, 6 #This is the third line of the data
... 7, 8
... # And here comes the last line
... 9, 0
... """
>>> np.genfromtxt(StringIO(data), comments="#", delimiter=",")
array([[1., 2.],
[3., 4.],
[5., 6.],
[7., 8.],
[9., 0.]])
New in version 1.7.0: Когда comments задано None, никакие строки не обрабатываются как комментарии.
Примечание
Существует одно важное исключение из этого поведения: если необязательный аргумент names=True, первая комментированная строка будет проверена на имена.
Пропуск строк и выбор столбцов
Аргумент usecols
В некоторых случаях нас интересуют не все столбцы данных, а только некоторые из них. Мы можем выбрать, какие столбцы импортировать, с помощью аргумента usecols . Этот аргумент принимает одно целое число или последовательность целых чисел, соответствующих индексам импортируемых столбцов. Помните, что по умолчанию первый столбец имеет индекс 0. Отрицательные целые числа ведут себя так же, как обычные отрицательные индексы Python.
Например, если мы хотим импортировать только первый и последний столбцы, мы можем использовать usecols=(0, -1):
>>> data = "1 2 3\n4 5 6"
>>> np.genfromtxt(StringIO(data), usecols=(0, -1))
array([[1., 3.],
[4., 6.]])
Если столбцы имеют имена, мы также можем выбрать столбцы для импорта, указав их имя в аргументе usecols , либо в виде последовательности строк, либо в виде строки, разделенной запятыми:
>>> data = "1 2 3\n4 5 6"
>>> np.genfromtxt(StringIO(data),
... names="a, b, c", usecols=("a", "c"))
array([(1., 3.), (4., 6.)], dtype=[('a', '<f8'), ('c', '<f8')])
>>> np.genfromtxt(StringIO(data),
... names="a, b, c", usecols=("a, c"))
array([(1., 3.), (4., 6.)], dtype=[('a', '<f8'), ('c', '<f8')])
Выбор типа данных
Основной способ управления тем, как последовательности строк, которые мы прочитали из файла, преобразуются в другие типы, заключается в установке аргумента dtype . Допустимые значения для этого аргумента:
- один тип, например,
dtype=float. Результатом будет 2D массив с указанным типом данных, если не было присвоено имя каждому столбцу с помощью аргументаnames(см. ниже). Обратите внимание, чтоdtype=floatявляется значением по умолчанию дляgenfromtxt. - последовательность типов, например,
dtype=(int, float, float). - строка, разделенная запятыми, например,
dtype="i4,f8,|U3". - словарь с двумя ключами
'names'и'formats'. - последовательность кортежей
(name, type), например,dtype=[('A', int), ('B', float)]. - существующий объект
numpy.dtype. - специальное значение
None. В этом случае тип столбцов будет определяться самими данными (см. ниже).
Во всех случаях, кроме первого, результатом будет 1D массив со структурированным типом данных. Этот тип данных имеет столько полей, сколько элементов в последовательности. Имена полей определяются с помощью ключевого слова names.
Когда dtype=None, тип каждого столбца определяется итеративно по его данным. Мы начинаем с проверки, можно ли строку преобразовать в булево значение (то есть, если строка соответствует true или false в нижнем регистре); затем, можно ли преобразовать ее в целое число, затем в число с плавающей точкой, затем в комплексное число и, в конечном итоге, в строку.
Опция dtype=None предоставлена для удобства. Однако она значительно медленнее, чем явное указание типа.
Установка имён
Аргумент names
Естественный подход при работе с табличными данными — назначить имя каждой колонке. Первый вариант — использовать явный структурированный тип данных, как упоминалось ранее:
>>> data = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, dtype=[(_, int) for _ in "abc"])
array([(1, 2, 3), (4, 5, 6)],
dtype=[('a', '<i8'), ('b', '<i8'), ('c', '<i8')])
Другой, более простой вариант — использовать ключевое слово names со списком строк или строкой с разделителями запятых:
>>> data = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, names="A, B, C")
array([(1., 2., 3.), (4., 5., 6.)],
dtype=[('A', '<f8'), ('B', '<f8'), ('C', '<f8')])
В примере выше мы использовали тот факт, что по умолчанию dtype=float. Передавая список имён, мы принуждаем выход к структурированному типу данных.
Иногда нам нужно определить имена колонок по самим данным. В этом случае необходимо использовать ключевое слово names со значением True. Имена будут затем считываться из первой строки (после skip_header строк), даже если эта строка прокомментирована:
>>> data = StringIO("So it goes\n#a b c\n1 2 3\n 4 5 6")
>>> np.genfromtxt(data, skip_header=1, names=True)
array([(1., 2., 3.), (4., 5., 6.)],
dtype=[('a', '<f8'), ('b', '<f8'), ('c', '<f8')])
Значение по умолчанию для names — None. Если мы зададим любое другое значение для ключевого слова, новые имена перепишут имена полей, определённые с помощью типа данных:
>>> data = StringIO("1 2 3\n 4 5 6")
>>> ndtype=[('a',int), ('b', float), ('c', int)]
>>> names = ["A", "B", "C"]
>>> np.genfromtxt(data, names=names, dtype=ndtype)
array([(1, 2., 3), (4, 5., 6)],
dtype=[('A', '<i8'), ('B', '<f8'), ('C', '<i8')])
Аргумент defaultfmt
Если names=None, но ожидается структурированный тип данных, имена определяются с использованием стандартного значения по умолчанию NumPy "f%i", что приводит к именам типа f0, f1 и так далее:
>>> data = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, dtype=(int, float, int))
array([(1, 2., 3), (4, 5., 6)],
dtype=[('f0', '<i8'), ('f1', '<f8'), ('f2', '<i8')])
Аналогично, если мы не предоставили достаточно имён для соответствия длине типа данных, недостающие имена будут определены с помощью этой шаблона по умолчанию:
>>> data = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, dtype=(int, float, int), names="a")
array([(1, 2., 3), (4, 5., 6)],
dtype=[('a', '<i8'), ('f0', '<f8'), ('f1', '<i8')])
Мы можем переопределить этот шаблон по умолчанию с помощью аргумента defaultfmt, который принимает любую строку формата:
>>> data = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, dtype=(int, float, int), defaultfmt="var_%02i")
array([(1, 2., 3), (4, 5., 6)],
dtype=[('var_00', '<i8'), ('var_01', '<f8'), ('var_02', '<i8')])
Примечание
Важно помнить, что defaultfmt используется только в том случае, если ожидаются некоторые имена, но они не определены.
Проверка имён
Массивы NumPy со структурированным типом данных также могут рассматриваться как recarray, где к полю можно получить доступ, как если бы оно было атрибутом. По этой причине нам может потребоваться убедиться, что имя поля не содержит пробелов или недопустимых символов, или что оно не соответствует имени стандартного атрибута (например, size или shape), что может сбить с толку интерпретатор. genfromtxt принимает три необязательных аргумента, которые обеспечивают более тонкий контроль над именами:
deletechars-
Указывает строку, объединяющую все символы, которые необходимо удалить из имени. По умолчанию недопустимые символы —
~!@#$%^&*()-=+~\|]}[{';: /?.>,<. excludelist-
Указывает список имён, которые следует исключить, таких как
return,file,print… Если одно из входных имён находится в этом списке, к нему будет добавлен символ нижнего подчёркивания ('_'). case_sensitive-
Определяет, должны ли имена быть регистрозависимыми (
case_sensitive=True), преобразованы в верхний регистр (case_sensitive=Falseилиcase_sensitive='upper'), или в нижний регистр (case_sensitive='lower').
Настройка преобразования
Аргумент converters
Обычно, определения типа данных достаточно для определения того, как последовательность строк должна быть преобразована. Однако иногда может потребоваться дополнительный контроль. Например, мы можем захотеть убедиться, что дата в формате YYYY/MM/DD преобразуется в объект datetime, или что строка типа xx% правильно преобразуется в число с плавающей точкой между 0 и 1. В таких случаях следует определить функции преобразования с помощью аргумента converters.
Значение этого аргумента обычно является словарем, в котором в качестве ключей используются индексы или имена колонок, а в качестве значений — функции преобразования. Эти функции преобразования могут быть как обычными функциями, так и лямбда-функциями. В любом случае они должны принимать только строку в качестве входных данных и возвращать только один элемент желаемого типа.
В следующем примере вторая колонка преобразуется из строки, представляющей процент, в число с плавающей точкой от 0 до 1:
>>> convertfunc = lambda x: float(x.strip("%"))/100.
>>> data = "1, 2.3%, 45.\n6, 78.9%, 0"
>>> names = ("i", "p", "n")
>>> # General case .....
>>> np.genfromtxt(StringIO(data), delimiter=",", names=names)
array([(1., nan, 45.), (6., nan, 0.)],
dtype=[('i', '<f8'), ('p', '<f8'), ('n', '<f8')])
Важно помнить, что по умолчанию dtype=float. Следовательно, для второй колонки ожидается число с плавающей точкой. Однако строки ' 2.3%' и ' 78.9%' не могут быть преобразованы в число с плавающей точкой, и в итоге получается np.nan. Теперь давайте воспользуемся преобразователем:
>>> # Converted case ...
>>> np.genfromtxt(StringIO(data), delimiter=",", names=names,
... converters={1: convertfunc})
array([(1., 0.023, 45.), (6., 0.789, 0.)],
dtype=[('i', '<f8'), ('p', '<f8'), ('n', '<f8')])
Тот же результат можно получить, используя имя второй колонки ("p") в качестве ключа вместо её индекса (1):
>>> # Using a name for the converter ...
>>> np.genfromtxt(StringIO(data), delimiter=",", names=names,
... converters={"p": convertfunc})
array([(1., 0.023, 45.), (6., 0.789, 0.)],
dtype=[('i', '<f8'), ('p', '<f8'), ('n', '<f8')])
Преобразователи также могут использоваться для предоставления значения по умолчанию для отсутствующих записей. В следующем примере преобразователь convert преобразует очищенную строку в соответствующее число с плавающей точкой или в -999, если строка пуста. Необходимо явно очищать строку от пробелов, так как это не делается по умолчанию:
>>> data = "1, , 3\n 4, 5, 6"
>>> convert = lambda x: float(x.strip() or -999)
>>> np.genfromtxt(StringIO(data), delimiter=",",
... converters={1: convert})
array([[ 1., -999., 3.],
[ 4., 5., 6.]])
Использование пропущенных и заполняемых значений
В наборе данных, который мы пытаемся импортировать, могут отсутствовать некоторые записи. В предыдущем примере мы использовали преобразователь для преобразования пустой строки в число с плавающей точкой. Однако пользовательские преобразователи могут быстро стать неудобными для управления.
Функция genfromtxt предоставляет два других дополнительных механизма: аргумент missing_values используется для распознавания пропущенных данных, а второй аргумент, filling_values, используется для обработки этих пропущенных данных.
missing_values
По умолчанию любая пустая строка отмечается как пропущенная. Мы также можем рассматривать более сложные строки, такие как "N/A" или "???" для представления пропущенных или некорректных данных. Аргумент missing_values принимает три вида значений:
- строка или строка с разделителями запятых
-
Эта строка будет использоваться в качестве маркера пропущенных данных для всех колонок
- последовательность строк
-
В этом случае каждый элемент связан с колонкой в порядке следования.
- словарь
-
Значения словаря — это строки или последовательности строк. Соответствующие ключи могут быть индексами колонок (целые числа) или именами колонок (строки). Кроме того, специальный ключ
Noneможет использоваться для определения значения по умолчанию, применимого ко всем колонкам.
filling_values
Мы знаем, как распознать пропущенные данные, но нам все равно нужно предоставить значение для этих пропущенных записей. По умолчанию это значение определяется из ожидаемого типа данных в соответствии с этой таблицей:
Ожидаемый тип | Значение по умолчанию |
|---|---|
|
|
|
|
|
|
|
|
|
|
Мы можем получить более тонкий контроль над преобразованием пропущенных значений с помощью необязательного аргумента filling_values. Как и missing_values, этот аргумент принимает различные типы значений:
- единственное значение
-
Это будет значением по умолчанию для всех колонок
- последовательность значений
-
Каждый элемент будет значением по умолчанию для соответствующей колонки
- словарь
-
Каждый ключ может быть индексом или именем колонки, а соответствующее значение должно быть единственным объектом. Мы можем использовать специальный ключ
Noneдля определения значения по умолчанию для всех колонок.
В следующем примере предполагается, что пропущенные значения помечены как "N/A" в первой колонке и "???" в третьей колонке. Мы хотим преобразовать эти пропущенные значения в 0, если они встречаются в первой и второй колонке, и в -999, если они встречаются в последней колонке:
>>> data = "N/A, 2, 3\n4, ,???"
>>> kwargs = dict(delimiter=",",
... dtype=int,
... names="a,b,c",
... missing_values={0:"N/A", 'b':" ", 2:"???"},
... filling_values={0:0, 'b':0, 2:-999})
>>> np.genfromtxt(StringIO(data), **kwargs)
array([(0, 2, 3), (4, 0, -999)],
dtype=[('a', '<i8'), ('b', '<i8'), ('c', '<i8')])
usemask
Мы также можем отслеживать случаи пропущенных данных, создав маску булевых значений, с True для пропущенных данных и False в противном случае. Для этого достаточно установить необязательный аргумент usemask в значение True (значение по умолчанию — False). В этом случае выходной массив будет массивом MaskedArray.
© 2005–2024 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/2.0/user/basics.io.genfromtxt.html