у меня есть стол
| user | list_1 | list_2 | list_3 | rank |
| -------- | -------- | -------- | -------- |-------- |
| xxx | [1,3,5] | [8,3,5] | [6,7,8] | [1,2,3] |
| yyy | [4,9,5] | [3,4,5] | [7,8,3] | [1,3,2] |
| zzz | [4,6,1] | [9,3,6] | [4,3,2] | [2,3,1] |
Столбец rank
является производным от ранга столбца list_1.
Я хотел бы отсортировать остальные столбцы list_2, list_3
, используя столбец ранга.
Желаемый результат должен быть
| user | list_1 | list_2 | list_3 | rank |
+------+---------+---------+---------+---------+
| xxx | [1,3,5] | [8,3,5] | [6,7,8] | [1,2,3] |
| yyy | [4,5,9] | [3,5,4] | [7,3,8] | [1,3,2] |
| zzz | [1,4,6] | [6,9,3] | [2,4,3] | [2,3,1] |
я пробовал различные функции .list
, но думаю, что они не могут принимать аргументы по строкам
Есть ли какая-нибудь функция в полярах, которая может это сделать? Очень признателен
import polars as pl
data = {
"user": ["xxx", "yyy", "zzz"],
"list_1": [[1, 3, 5], [4, 9, 5], [4, 6, 1]],
"list_2": [[8, 3, 5], [3, 4, 5], [9, 3, 6]],
"list_3": [[6, 7, 8], [7, 8, 3], [4, 3, 2]],
"rank": [[1, 2, 3], [1, 3, 2], [2, 3, 1]]
}
df = pl.DataFrame(data)
print(df)
Вы можете разобрать, отсортировать по группам пользователей, а затем объединить обратно в списки:
df = pl.DataFrame({'user': ['xxx', 'yyy', 'zzz'],
'list_1': [[1, 3, 5], [4, 9, 5], [4, 6, 1]],
'list_2': [[8, 3, 5], [3, 4, 5], [9, 3, 6]],
'list_3': [[6, 7, 8], [7, 8, 3], [4, 3, 2]],
'rank': [[1, 2, 3], [1, 3, 2], [2, 3, 1]]})
cols = ['list_1', 'list_2', 'list_3', 'rank']
rank = df['rank']
out = (df.explode(cols).select(pl.all().sort_by('rank').over('user'))
.group_by('user', maintain_order=True).agg(pl.col(cols))
.with_columns(rank=rank)
)
Выход:
┌──────┬───────────┬───────────┬───────────┬───────────┐
│ user ┆ list_1 ┆ list_2 ┆ list_3 ┆ rank │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ list[i64] ┆ list[i64] ┆ list[i64] ┆ list[i64] │
╞══════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ xxx ┆ [1, 3, 5] ┆ [8, 3, 5] ┆ [6, 7, 8] ┆ [1, 2, 3] │
│ yyy ┆ [4, 5, 9] ┆ [3, 5, 4] ┆ [7, 3, 8] ┆ [1, 3, 2] │
│ zzz ┆ [1, 4, 6] ┆ [6, 9, 3] ┆ [2, 4, 3] ┆ [2, 3, 1] │
└──────┴───────────┴───────────┴───────────┴───────────┘
оригинальный ответ
Предполагая списки Python, вам придется перебирать их и переупорядочивать:
cols = ['list_1', 'list_2', 'list_3']
for c in cols:
df[c] = [[l[i-1] for i in r] for l, r in zip(df[c], df['rank'])]
Выход:
user list_1 list_2 list_3 rank
0 xxx [1, 3, 5] [8, 3, 5] [6, 7, 8] [1, 2, 3]
1 yyy [4, 5, 9] [3, 5, 4] [7, 3, 8] [1, 3, 2]
2 zzz [6, 1, 4] [3, 6, 9] [3, 2, 4] [2, 3, 1]
Обратите внимание, что в вашем примере ранги кажутся неверными. Если вам нужно вычислить ранг:
cols = ['list_1', 'list_2', 'list_3']
df['rank'] = [(np.argsort(x)+1).tolist() for x in df['list_1']]
for c in cols:
df[c] = [[l[i-1] for i in r] for l, r in zip(df[c], df['rank'])]
Выход:
user list_1 list_2 list_3 rank
0 xxx [1, 3, 5] [8, 3, 5] [6, 7, 8] [1, 2, 3]
1 yyy [4, 5, 9] [3, 5, 4] [7, 3, 8] [1, 3, 2]
2 zzz [1, 4, 6] [6, 9, 3] [2, 4, 3] [3, 1, 2]
Если весь ваш список имеет одинаковый размер, вы также можете использовать промежуточный 3D numpy массив, argsort и take_along_axis:
a = np.array(df[cols].to_numpy().tolist())
order = np.argsort(a[:, 0], axis=1)
df['rank'] = (order+1).tolist()
df[cols] = np.take_along_axis(a, a[:, [0]].argsort(axis=-1), axis=-1).tolist()
Выход:
user list_1 list_2 list_3 rank
0 xxx [1, 3, 5] [8, 3, 5] [6, 7, 8] [1, 2, 3]
1 yyy [4, 5, 9] [3, 5, 4] [7, 3, 8] [1, 3, 2]
2 zzz [1, 4, 6] [6, 9, 3] [2, 4, 3] [3, 1, 2]
Я думаю, ваше решение работает для панд, но мне интересно, есть ли способ сделать это в полярах без необходимости конвертировать для этого в панды.
@benedictine_cumbersome К сожалению, я пропустил тег Polars; Можете ли вы предоставить минимальный воспроизводимый пример в виде кода?
Я новичок в этом. Считается ли приведенный выше фрейм данных MRE?
@benedictine_cumbersome да, спасибо, я обновил свой ответ. Также я бы рекомендовал опубликовать текстовый вывод поляров, чтобы сразу было понятно, что вы используете поляры;)
раздел полярников заработал! Большое спасибо
Альтернативой решению с использованием шаблона explode()
, group_by()
, agg()
можно использовать pl.Expr.list.gather, чтобы выбирать элементы из каждого списка по порядку.
Для этого мы преобразуем ранги, используя pl.Expr.list.eval и pl.Expr.arg_sort, чтобы получить список индексов, выбирающих элементы в порядке, предложенном rank
.
cols = ["list_1", "list_2", "list_3"]
df.with_columns(
pl.col(cols).list.gather(
pl.col("rank").list.eval(pl.element().arg_sort())
)
)
shape: (3, 5)
┌──────┬───────────┬───────────┬───────────┬───────────┐
│ user ┆ list_1 ┆ list_2 ┆ list_3 ┆ rank │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ list[i64] ┆ list[i64] ┆ list[i64] ┆ list[i64] │
╞══════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ xxx ┆ [1, 3, 5] ┆ [8, 3, 5] ┆ [6, 7, 8] ┆ [1, 2, 3] │
│ yyy ┆ [4, 5, 9] ┆ [3, 5, 4] ┆ [7, 3, 8] ┆ [1, 3, 2] │
│ zzz ┆ [1, 4, 6] ┆ [6, 9, 3] ┆ [2, 4, 3] ┆ [2, 3, 1] │
└──────┴───────────┴───────────┴───────────┴───────────┘
Хороший вариант, но я бы, наверное, изменил его на pl.col("rank").list.eval(pl.element() - 1
. Мне просто кажется более аккуратным. arg_sort()
сработало бы на текущем примере, но сработает и, например, на rank = [-100, 100, 200] instead of [1,2,3]
, что, я бы сказал, не является намерением.
@RomanPekar Правда... Сначала у меня было pl.element() - 1
локально, но я переключился на более общее решение. Однако простое уменьшение ранга, вероятно, также будет гораздо более эффективным. Я отредактирую ответ, упомянув альтернативу, как только перестану пользоваться мобильным телефоном.
Если вы немного скорректируете свой расчет rank
, чтобы он основывался на 0
df = df.with_columns(
rank = pl.col.list_1.list.eval(pl.element().rank().cast(pl.Int32) - 1)
)
┌──────┬───────────┬───────────┬───────────┬───────────┐
│ user ┆ list_1 ┆ list_2 ┆ list_3 ┆ rank │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ list[i64] ┆ list[i64] ┆ list[i64] ┆ list[i32] │
╞══════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ xxx ┆ [1, 3, 5] ┆ [8, 3, 5] ┆ [6, 7, 8] ┆ [0, 1, 2] │
│ yyy ┆ [4, 9, 5] ┆ [3, 4, 5] ┆ [7, 8, 3] ┆ [0, 2, 1] │
│ zzz ┆ [4, 6, 1] ┆ [9, 3, 6] ┆ [4, 3, 2] ┆ [1, 2, 0] │
└──────┴───────────┴───────────┴───────────┴───────────┘
rank
.cols = ["list_1", "list_2", "list_3"]
df.with_columns(
pl.col(cols).list.gather(pl.col.rank)
)
┌──────┬───────────┬───────────┬───────────┬───────────┐
│ user ┆ list_1 ┆ list_2 ┆ list_3 ┆ rank │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ list[i64] ┆ list[i64] ┆ list[i64] ┆ list[i32] │
╞══════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ xxx ┆ [1, 3, 5] ┆ [8, 3, 5] ┆ [6, 7, 8] ┆ [0, 1, 2] │
│ yyy ┆ [4, 5, 9] ┆ [3, 5, 4] ┆ [7, 3, 8] ┆ [0, 2, 1] │
│ zzz ┆ [6, 1, 4] ┆ [3, 6, 9] ┆ [3, 2, 4] ┆ [1, 2, 0] │
└──────┴───────────┴───────────┴───────────┴───────────┘
Альтернативно вы можете настроить существующие rank
данные и работать с исходным фреймом данных:
df.with_columns(
pl.col(cols).list.gather(
pl.col("rank").list.eval(pl.element() - 1)
)
)
┌──────┬───────────┬───────────┬───────────┬───────────┐
│ user ┆ list_1 ┆ list_2 ┆ list_3 ┆ rank │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ list[i64] ┆ list[i64] ┆ list[i64] ┆ list[i64] │
╞══════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ xxx ┆ [1, 3, 5] ┆ [8, 3, 5] ┆ [6, 7, 8] ┆ [1, 2, 3] │
│ yyy ┆ [4, 5, 9] ┆ [3, 5, 4] ┆ [7, 3, 8] ┆ [1, 3, 2] │
│ zzz ┆ [6, 1, 4] ┆ [3, 6, 9] ┆ [3, 2, 4] ┆ [2, 3, 1] │
└──────┴───────────┴───────────┴───────────┴───────────┘
Что-то не так с вашим примером: либо ранг zzz неверен, либо ваш вывод неверен.