Первый вопрос: хотите ли вы кодировать каждый столбец отдельно или кодировать их все одной кодировкой?
Выражение df = df.astype(str).apply(LabelEncoder().fit_transform)
подразумевает, что вы кодируете все столбцы по отдельности.
That case you can do the following:
df = df.apply(lambda series: pd.Series(
LabelEncoder().fit_transform(series[series.notnull()]),
index=series[series.notnull()].index
))
print(df)
Out:
A B C
0 0.0 0 1.0
1 NaN 1 0.0
2 1.0 2 NaN
объяснение того, как это работает ниже.Но для начала я расскажу о нескольких недостатках этого решения.
Недостатки
Во-первых, существуют смешанные типы столбцов: если столбец содержит NaN
значение, тогда столбец имеет тип float
, потому что nan's - это числа с плавающей запятой в python.
df.dtypes
A float64
B int64
C float64
dtype: object
Кажется, для меток это бессмысленно.Хорошо, позже вы можете игнорировать все наны и преобразовать остальные в целое число.
Второй момент: вероятно, вам нужно запомнить LabelEncoder
- потому что часто требуется, например, сделать обратное преобразование.Но это решение не запоминает кодировщики, у вас нет такой возможности.
Простое, явное решение:
encoders = dict()
for col_name in df.columns:
series = df[col_name]
label_encoder = LabelEncoder()
df[col_name] = pd.Series(
label_encoder.fit_transform(series[series.notnull()]),
index=series[series.notnull()].index
)
encoders[col_name] = label_encoder
print(df)
Out:
A B C
0 0.0 0 1.0
1 NaN 1 0.0
2 1.0 2 NaN
- больше кода, но результат тот же
print(encoders)
Out
{'A': LabelEncoder(), 'B': LabelEncoder(), 'C': LabelEncoder()}
- также доступны кодеры.Обратное преобразование (должно быть также удалено до Nan!):
encoders['B'].inverse_transform(df['B'])
Out:
array([1, 6, 9])
Кроме того, некоторые опции, такие как некоторый суперкласс реестра для кодировщиков, также доступны, и они совместимы с первым решением, но их легче перебирать по столбцам.
Как это работает
df.apply(lambda series: ...)
применяет функцию, которая возвращает pd.Series
для каждого столбца;таким образом, он возвращает фрейм данных с новыми значениями.
Выражение шаг за шагом:
pd.Series(
LabelEncoder().fit_transform(series[series.notnull()]),
index=series[series.notnull()].index
)
- series[series.notnull()]
отбрасывает NaN
значений, затем передает остаток в fit_transform
.
- поскольку кодировщик меток возвращает numpy.array
и выбрасывает индекс, index=series[series.notnull()].index
восстанавливает его для правильной конкатенации.Если не выполнять индексацию:
print(df)
Out:
A B C
0 x 1 2.0
1 NaN 6 1.0
2 z 9 NaN
df = df.apply(lambda series: pd.Series(
LabelEncoder().fit_transform(series[series.notnull()]),
))
print(df)
Out:
A B C
0 0.0 0 1.0
1 1.0 1 0.0
2 NaN 2 NaN
- значения смещаются от правильных позиций - и даже может IndexError
.
Один кодировщик для всех столбцов
В этом случае стекируйте фрейм данных, подгоняйте кодировку, затем снимайте ее
series_stack = df.stack().astype(str)
label_encoder = LabelEncoder()
df = pd.Series(
label_encoder.fit_transform(series_stack),
index=series_stack.index
).unstack()
print(df)
Out:
A B C
0 5.0 0.0 2.0
1 NaN 3.0 1.0
2 6.0 4.0 NaN
- так как series_stack
равен pd.Series
, содержащему NaN
, все значения из DataFrame являются плавающими, так что вы можете предпочесть конвертировать его.
Надеюсь, это поможет.