Базовый сценарий
Для службы рекомендаций я обучаю матричную модель факторизации (LightFM) на множестве взаимодействий пользователь-элемент.Чтобы матричная модель факторизации дала наилучшие результаты, мне нужно сопоставить мои идентификаторы пользователя и элемента с непрерывным диапазоном целочисленных идентификаторов, начиная с 0.
В процессе я использую DataFrame от pandas, и яЯ обнаружил, что MultiIndex чрезвычайно удобен для создания этого отображения, например:
ratings = [{'user_id': 1, 'item_id': 1, 'rating': 1.0},
{'user_id': 1, 'item_id': 3, 'rating': 1.0},
{'user_id': 3, 'item_id': 1, 'rating': 1.0},
{'user_id': 3, 'item_id': 3, 'rating': 1.0}]
df = pd.DataFrame(ratings, columns=['user_id', 'item_id', 'rating'])
df = df.set_index(['user_id', 'item_id'])
df
Out:
rating
user_id item_id
1 1 1.0
1 3 1.0
3 1 1.0
3 1 1.0
, а затем позволяет мне получать непрерывные карты примерно так:
df.index.labels[0] # For users
Out:
FrozenNDArray([0, 0, 1, 1], dtype='int8')
df.index.labels[1] # For items
Out:
FrozenNDArray([0, 1, 0, 1], dtype='int8')
После этого я могу отобразитьих обратно, используя метод df.index.levels[0].get_loc
.Отлично!
Расширение
Но сейчас я пытаюсь упростить процесс обучения моей модели, в идеале, постепенно обучая его новым данным, сохраняя старые сопоставления идентификаторов.Примерно так:
new_ratings = [{'user_id': 2, 'item_id': 1, 'rating': 1.0},
{'user_id': 2, 'item_id': 2, 'rating': 1.0}]
df2 = pd.DataFrame(new_ratings, columns=['user_id', 'item_id', 'rating'])
df2 = df2.set_index(['user_id', 'item_id'])
df2
Out:
rating
user_id item_id
2 1 1.0
2 2 1.0
Затем, просто добавив новые рейтинги к старому DataFrame
df3 = df.append(df2)
df3
Out:
rating
user_id item_id
1 1 1.0
1 3 1.0
3 1 1.0
3 3 1.0
2 1 1.0
2 2 1.0
Выглядит хорошо, но
df3.index.labels[0] # For users
Out:
FrozenNDArray([0, 0, 2, 2, 1, 1], dtype='int8')
df3.index.labels[1] # For items
Out:
FrozenNDArray([0, 2, 0, 2, 0, 1], dtype='int8')
Я добавил user_id = 2и item_id = 2 в последующем DataFrame специально, чтобы проиллюстрировать, где это происходит для меня.В df3
метки 3 (как для пользователя, так и для элемента) переместились из целочисленной позиции 1 в 2. Таким образом, отображение больше не совпадает.Я ищу [0, 0, 1, 1, 2, 2]
и [0, 1, 0, 1, 0, 2]
для сопоставления пользователя и элемента соответственно.
Это, вероятно, из-за упорядочения в индексных объектах панд, и я не уверен, что то, что я хочу, вообщевозможно использование стратегии MultiIndex.Нужна помощь в том, как наиболее эффективно решить эту проблему:)
Некоторые примечания:
- Я считаю использование DataFrames удобным по нескольким причинам, но я использую MultiIndex исключительно для сопоставления идентификаторов,Альтернативы без MultiIndex полностью приемлемы.
- Я не могу гарантировать, что новые записи user_id и item_id в новых рейтингах больше, чем любые значения в старом наборе данных, поэтому мой пример добавления id 2, когда присутствовали [1, 3].
- Для моего подхода к инкрементальному обучению мне нужно где-то хранить свои идентификационные карты.Если я только частично загружаю новые рейтинги, мне придется где-то хранить старые карты данных и IDFrame.Было бы замечательно, если бы все это могло быть в одном месте, как это было бы с индексом, но столбцы тоже работают.
- РЕДАКТИРОВАТЬ: Дополнительное требование состоит в том, чтобы разрешить переупорядочение строк исходного DataFrame, так какможет случиться, если существуют повторяющиеся рейтинги, и я хочу сохранить самый последний рейтинг.
Решение (кредиты @jpp для оригинала)
Я внес изменение в @ jpp'sответ, чтобы удовлетворить дополнительное требование, которое я добавил позже (помеченный как РЕДАКТИРОВАТЬ).Это также действительно удовлетворяет первоначальному вопросу, поставленному в заголовке, поскольку сохраняет старые целочисленные позиции индекса независимо от того, по каким причинам строки были переупорядочены.Я также обернул вещи в функции:
from itertools import chain
from toolz import unique
def expand_index(source, target, index_cols=['user_id', 'item_id']):
# Elevate index to series, keeping source with index
temp = source.reset_index()
target = target.reset_index()
# Convert columns to categorical, using the source index and target columns
for col in index_cols:
i = source.index.names.index(col)
col_cats = list(unique(chain(source.index.levels[i], target[col])))
temp[col] = pd.Categorical(temp[col], categories=col_cats)
target[col] = pd.Categorical(target[col], categories=col_cats)
# Convert series back to index
source = temp.set_index(index_cols)
target = target.set_index(index_cols)
return source, target
def concat_expand_index(old, new):
old, new = expand_index(old, new)
return pd.concat([old, new])
df3 = concat_expand_index(df, df2)
Результат:
df3.index.labels[0] # For users
Out:
FrozenNDArray([0, 0, 1, 1, 2, 2], dtype='int8')
df3.index.labels[1] # For items
Out:
FrozenNDArray([0, 1, 0, 1, 0, 2], dtype='int8')