Найти «самый сильный» тип для каждого столбца в CSV - PullRequest
0 голосов
/ 25 апреля 2018

Мне нужно отсканировать 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 в это поле.в противном случае файл является недействительным).

Для этого я делаю следующее:

  1. Для каждого файла читайте файл строка за строкой и проверяйте каждый столбец;store "strongest" type
  2. Создать новый контейнер, который содержит новые типы отлитых строк

Элемент 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')

1 Ответ

0 голосов
/ 25 апреля 2018

Пример DataFrame:

In [11]: df
Out[11]:
    C1  C2 C3    C4
R1   1   a  6   8.0
R2   2  4.  7   9.0
R3   3   5  b  10.0

Я бы не пытался быть умным с оценкой короткого замыкания. Я бы просто взял тип каждой записи:

In [12]: df_types = df.applymap(type)

In [13]: df_types
Out[13]:
               C1             C2             C3               C4
R1  <class 'int'>  <class 'str'>  <class 'str'>  <class 'float'>
R2  <class 'int'>  <class 'str'>  <class 'str'>  <class 'float'>
R3  <class 'int'>  <class 'str'>  <class 'str'>  <class 'float'>

Если вы перечислите эти типы, вы можете использовать max:

In [14]: d = {ch: i for i, ch in enumerate([int, float, str])}

In [15]: d_inv = {i: ch for i, ch in enumerate([int, float, str])}

In [16]: df_types.applymap(d.get)
Out[16]:
    C1  C2  C3  C4
R1   0   2   2   1
R2   0   2   2   1
R3   0   2   2   1

In [17]: df_types.applymap(d.get).max()
Out[17]:
C1    0
C2    2
C3    2
C4    1
dtype: int64

In [18]: df_types.applymap(d.get).max().apply(d_inv.get)
Out[18]:
C1      <class 'int'>
C2      <class 'str'>
C3      <class 'str'>
C4    <class 'float'>
dtype: object

Теперь вы можете перебирать каждый столбец и обновлять его в df (до максимума):

In [21]: for col, typ in df_types.applymap(d.get).max().apply(d_inv.get).iteritems():
             df[col] = df[col].astype(typ)


In [22]: df
Out[22]:
    C1  C2 C3    C4
R1   1   a  6   8.0
R2   2  4.  7   9.0
R3   3   5  b  10.0

In [23]: df.dtypes
Out[23]:
C1      int64
C2     object
C3     object
C4    float64
dtype: object

Это можно сделать несколько более эффективным, если у вас есть много столбцов путем группировки по типу и обновления пакетов столбцов (например, всех строковых столбцов одновременно).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...