Рубрики
Без рубрики

Уничтожительные типы данных с пандами

Недавно мне пришлось найти способ уменьшить след падах DataFrame, чтобы Actu … Tagged с пандами, Python.

Недавно мне пришлось найти способ уменьшить след падах DataFrame Pandas, чтобы фактически выполнять операции. Вот трюк, который пригодился!

По умолчанию, если вы прочитаете DataFrame из файла, он будет разыграть все числовые столбцы в виде float64 тип. Это соответствует философии, стоящей за Pandas и Numpy, – с помощью строгих типов (вместо обычного питона «напечатка утки»), вы можете делать вещи намного быстрее. float64 является наиболее гибким численным типом – он может обрабатывать фракции, а также превращать недостающие значения в НАН Анкет Это позволит нам прочитать его в память, а затем начнет с ним возиться. Недостатком является то, что он потребляет много памяти.

Теперь, допустим, мы хотим сохранить память, вручную понизить наши столбцы в самый маленький тип, который может обрабатывать его значения? И давайте также скажем, что мы хотим быть действительно, очень ленивыми и не хотим смотреть на кучу чисел вручную. И допустим, мы хотим сделать это с помощью цепочки методов, из -за всех преимуществ, изложенных здесь: https://tomaugspurger.github.io/method-chaining

Давайте представим наш пример DataFrame. Мы преобразуем все значения в плавание вручную, потому что это то, что является по умолчанию, когда мы читаем из файла.

df = pd.DataFrame({
    "stay_float": [0.5, 3.7, 7.5],
    "to_int": [-5, 7, 5],
    "to_uint": [1, 100, 200]}).astype(float)

Во -первых, давайте представим рабочую лошадку этого упражнения – Pandas’s to_numeric функция и его удобный необязательный аргумент, вниз Анкет Это займет числовой тип – float , целое число (не int ), или без подписи – а затем спустите его до самой маленькой доступной версии.

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

def float_to_int(ser):
    try:
        int_ser = ser.astype(int)
        if (ser == int_ser).all():
            return int_ser
        else:
            return ser
    except ValueError:
        return ser

Мы используем здесь Try/кроме шаблона, потому что, если мы попытаемся сделать столбец с НАН Значения в целочисленный столбец, он принесет ошибку. Если бы в противном случае было бы хорошим кандидатом для превращения в целое число, мы должны рассмотреть значение для вмены этих пропущенных значений – но это будет отличаться для каждого столбца. Иногда имеет смысл сделать это 0, а иногда – средним или медианом колонны, или что -то еще.

Я также хотел бы направить ваше внимание на строку 4, которая имеет очень полезный шаблон Pandas – if (ser) .ll () Анкет Когда вы выполняете операции на столбцах Pandas, таких как равные или больше, чем вы получаете новый столбец, в котором операция была приложена элементом за элементом. Если вы пытаетесь настроить условное, интерпретатор не знает, что делать с массивом, содержащим [True, false, true] – Вы должны сводить его к одному значению. Итак, если вы хотите проверить, являются ли два столбца абсолютно равны, вы должны вызвать .все () Метод (который имеет полезный брат, any () ), чтобы создать условное, которое можно фактически использовать для управления выполнением.

Далее, давайте сделаем функцию, которая позволяет нам применить преобразование к нескольким столбцам на основе условия. назначить Метод довольно удивитель, и было бы весело не оставлять его (или, если мы это сделаем, хотя бы заменить его функцией, которую мы можем подготовить как часть цепочки преобразований в DataFrame в целом).

def multi_assign(df, transform_fn, condition):
    df_to_use = df.copy()

    return (df_to_use
        .assign(
            **{col: transform_fn(df_to_use[col])
               for col in condition(df_to_use)})
           )

назначить Позвольте нам выполнять несколько назначений, если мы делаем словарь имен столбцов и целевых значений, а затем распаковываем его. Действительно, на самом деле было бы легче пропустить функцию и перейти непосредственно к использованию этого синтаксиса, за исключением того, что я не знаю о методе доступа к фильтруемым списку столбцов DF, пока все еще »в« цепочке ». Я думаю, что будущие версии синтаксиса Pandas будут включать это, так как я читал, что они хотят поддерживать больше цепочки методов. Лично я нахожу, что снижение когнитивной нагрузки стоит того, при этом есть много маленьких модульных преобразований Lego Peece, прикованных вместе.

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

def all_float_to_int(df):
    df_to_use = df.copy()
    transform_fn = float_to_int
    condition = lambda x: list(x
                    .select_dtypes(include=["float"])
                    .columns)    

    return multi_assign(df_to_use, transform_fn, condition)

Смотрите шаблон в действии! Мы решаем функцию преобразования, мы решаем, какие условия мы хотим применить все эти преобразования (у нас может быть сотня столбцов, и кто хочет сделать заметку всего этого?), А затем мы передаем его Многоассина функция

(df
     .pipe(all_float_to_int)).dtypes


stay_float float64
to_int int64
to_uint int64
dtype: object

Прохладный! Но на самом деле мы не уменьшили размер нашего отдела данных – 64 байта целого числа занимают столько байтов, сколько 64 байта поплавка, точно так же, как сто фунтов перьев весит до сто фунтов кирпичей. То, что мы сделали, это облегчало смягчение этих столбцов позже.

Затем давайте сделаем функцию, которая принимает подмножество столбцов, и пытается понизить ее до самой маленькой версии, которую она может. У нас здесь довольно небольшие ценности, так что это должно выполнить некоторую работу.

def downcast_all(df, target_type, inital_type=None):
    #Gotta specify floats, unsigned, or integer
    #If integer, gotta be 'integer', not 'int'
    #Unsigned should look for Ints
    if inital_type is None:
        inital_type = target_type

    df_to_use = df.copy()

    transform_fn = lambda x: pd.to_numeric(x, 
                                downcast=target_type)

    condition = lambda x: list(x
                    .select_dtypes(include=[inital_type])
                    .columns) 

    return multi_assign(df_to_use, transform_fn, condition)

Тот же базовый шаблон, что и раньше! Но теперь у нас есть два аргумента – один – это target_type , что говорит нам, к каким типам попытаться смягчить. По умолчанию это будет то же самое, что initial_type , за одним исключением, которое мы возьмем за секунду!

(df
     .pipe(all_float_to_int)
     .pipe(downcast_all, "float")
     .pipe(downcast_all, "integer")
).dtypes


stay_float float32
to_int int8
to_uint int16
dtype: object

Хорошо, теперь мы где -то получаем! Интересно, сможем ли мы сделать еще лучше? Этот последний столбец имеет заметное имя! И он не имеет значений ниже 0 – Может быть, мы могли бы сэкономить место, если храним его как неподписанное целое число! Давайте добавим трубу в нашу цепь, которая попытается понизить определенные целые числа в безрецептурных …

(df
     .pipe(all_float_to_int)
     .pipe(downcast_all, "float")
     .pipe(downcast_all, "integer")
     .pipe(downcast_all,  
           target_type = "unsigned", 
           inital_type = "integer")
).dtypes


stay_float float32
to_int int8
to_uint uint8
dtype: objec

Что ты знаешь, мы можем!

Давайте посмотрим, сколько памяти мы сохраняем, делая это.

df.info(memory_usage='deep')



RangeIndex: 3 entries, 0 to 2
Data columns (total 3 columns):
stay_float 3 non-null float64
to_int 3 non-null float64
to_uint 3 non-null float64
dtypes: float64(3)
memory usage: 152.0 bytes

против

(df
     .pipe(all_float_to_int)
     .pipe(downcast_all, "float")
     .pipe(downcast_all, "integer")
     .pipe(downcast_all,  
           target_type = "unsigned", 
           inital_type = "integer")
).info(memory_usage='deep')



RangeIndex: 3 entries, 0 to 2
Data columns (total 3 columns):
stay_float 3 non-null float32
to_int 3 non-null int8
to_uint 3 non-null uint8
dtypes: float32(1), int8(1), uint8(1)
memory usage: 98.0 bytes

152 до 98 – мы сократили его более чем на 1/3!

Оригинал: “https://dev.to/hackersandslackers/downcast-numerical-data-types-with-pandas-d19”