Мне нужно отсканировать CSV по столбцу и найти самый сильный тип данных, а затем применить его ко всему столбцу.
Например, если у меня был CSV, который выглядел так (да, я не тамбез запятых ...):
+ C1 + C2 + C3 + C4
R1 | i | s | i | f
R2 | i | f | i | i
R3 | i | i | s | f
# i = int
# f = float
# s = str
Самый сильный тип для C1
будет i
, C2
будет s
, C3
будет s
иC4
будет f
.
Из этого следует, что порядок «силы» равен str > float > int
.
Почему?Поскольку тип файла, в который я записываю эти значения, явно требует, чтобы тип данных, указанный для поля (его столбцов), соответствовал этому типу данных (т. Е. Если для поля установлено значение FLOAT
, я не могу поместить str
в это поле.в противном случае файл является недействительным).
Для этого я делаю следующее:
- Для каждого файла читайте файл строка за строкой и проверяйте каждый столбец;store "strongest" type
- Создать новый контейнер, который содержит новые типы отлитых строк
Элемент 2 очень прост в использовании со словарем и списком:
types = {header: None for header in r.fieldnames}
# read file and store "strongest" found in 'types[header]' per column
# ...
typed = [[types[header](row[header]) for header in types] for row in rows]
# note: types[header] value is a function alias (i.e. int vs int())
Элемент 1 - это место, где происходит большая часть тяжелой работы:
for row in r: # r is a csv.DictReader
rows.append(row) # list of OrderedDicts since r is a generator
# problematic because I have to keep checking just to append...
if all(types[header] is str for header in types):
continue # all 'str' so stop checking
for header in types:
if types[header] is str:
continue # whole column can be bypassed from now on
# function just type casts 'int' or 'float' on string by ValueError
t = self.find_type(row[header])
if (types[header] is int) and (t is float):
types[header] = t # float > int since all int's can be represented as float
elif (types[header] is float) and (t is int):
pass # int < float so do nothing
else:
types[header] = t # if 'str' will be caught later by first if
Худшим случаем для выполнения этого является количество строк в CSV, поскольку последняя строка может содержать действительное значение * 1037.* type test.
Есть ли более эффективный способ сделать это, возможно, с помощью pandas
(в настоящее время не используйте много)?
Решение:
from numpy import issubdtype
from numpy import integer
from numpy import floating
from pandas import read_csv
from shapefile import Writer # PyShp library
df = read_csv('/some/file', low_memory = False)
rows = df.values.tolist() # fastest access over df.iterrows()
w = Writer(5, True)
# This is the core of the question
# I can access df[col].dtype but I didn't want to use str == str
# If this can be done better than subtype check let me know
for col in df:
if issubdtype(df[col], integer):
w.field(col, 'N', 20, 0)
elif issubdtype(df[col][0], floating):
w.field(col, 'F', 20, 10)
else:
w.field(col, 'C', 40, 0)
# Alternatively (1):
# from numpy import int64
# from numpy import float64
# for col in df:
# if df[col].dtype.type is int64:
# w.field(col, 'N', 20, 0)
# elif df[col].dtype.type is float64:
# w.field(col, 'F', 20, 10)
# else:
# w.field(col, 'C', 40, 0)
# Alternatively (2):
# Don't import numpy directly in namespace
# for col in df:
# if df[col].dtype == 'int64':
# w.field(col, 'N', 20, 0)
# elif df[col].dtype == 'float64':
# w.field(col, 'F', 20, 10)
# else:
# w.field(col, 'C', 40, 0)
lon = df.columns.get_loc('LON')
lat = df.columns.get_loc('LAT')
for row in rows:
w.point(row[lon], row[lat])
w.record(*row)
w.save('/some/outfile')