Я борюсь с поляками. У меня есть фрейм данных и массив numpy. Я хотел бы их вычесть.
import polars as pl
import pandas as pd
df = pl.DataFrame(np.random.randn(6, 4), schema=['#', 'x', 'y', 'z'])
arr = np.array([-10, -20, -30])
df.select(
pl.col(r'^[x|y|z]$')
).apply(
lambda x: np.array(x) - arr
)
shape: (6, 3)
┌───────────┬───────────┬───────────┐
│ column_0 ┆ column_1 ┆ column_2 │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞═══════════╪═══════════╪═══════════╡
│ 10.143819 ┆ 21.875335 ┆ 29.682364 │
│ null ┆ null ┆ null │
│ null ┆ null ┆ null │
│ null ┆ null ┆ null │
│ null ┆ null ┆ null │
│ null ┆ null ┆ null │
└───────────┴───────────┴───────────┘
Так что теперь вычитание применяется только к первой строке.
Но если я попытаюсь вычислить норму, например, то это работает для каждой строки:
df.select(
pl.col(r'^[x|y|z]$')
).apply(
lambda x: np.sum((np.array(x) - arr)**2)**0.5
)
shape: (6, 1)
┌───────────┐
│ apply │
│ --- │
│ f64 │
╞═══════════╡
│ 38.242255 │
│ 37.239545 │
│ 38.07624 │
│ 36.688312 │
│ 38.419194 │
│ 36.262196 │
└───────────┘
# check if it is correct:
np.sum((df.to_pandas()[['x', 'y', 'z']].to_numpy() - arr)**2, axis=1) ** 0.5
>>> array([38.24225488, 37.23954478, 38.07623986, 36.68831161, 38.41919409,
36.2621962 ])
В пандах это можно сделать так:
df.to_pandas()[['x', 'y', 'z']] - arr
x y z
0 10.143819 21.875335 29.682364
1 10.360651 21.116404 28.871060
2 9.777666 20.846593 30.325185
3 9.394726 19.357053 29.716592
4 9.223525 21.618511 30.390805
5 9.751234 21.667080 27.393393
Один из способов, которым это будет работать, — сделать это для каждого столбца отдельно. Но это означает, что много одного и того же кода, особенно когда количество столбцов увеличивается:
df.select(
pl.col('x') - arr[0], pl.col('y') - arr[1], pl.col('z') - arr[2]
)
Нет, это не сработало, он вычел arr[0] из всех столбцов.
Вы можете сопоставить вывод pandas
In [15]: df.to_pandas()[['x', 'y', 'z']] - arr
Out[15]:
x y z
0 10.342991 21.258934 29.083287
1 10.136803 21.543558 28.168207
2 11.900141 19.557348 29.490541
3 9.192346 19.498689 28.195094
4 9.219745 20.330358 29.005278
5 11.853378 19.458095 30.357041
с
In [17]: df.select([pl.col(col)-arr[i] for i, col in enumerate(['x', 'y', 'z'])])
Out[17]:
shape: (6, 3)
┌───────────┬───────────┬───────────┐
│ x ┆ y ┆ z │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞═══════════╪═══════════╪═══════════╡
│ 10.342991 ┆ 21.258934 ┆ 29.083287 │
│ 10.136803 ┆ 21.543558 ┆ 28.168207 │
│ 11.900141 ┆ 19.557348 ┆ 29.490541 │
│ 9.192346 ┆ 19.498689 ┆ 28.195094 │
│ 9.219745 ┆ 20.330358 ┆ 29.005278 │
│ 11.853378 ┆ 19.458095 ┆ 30.357041 │
└───────────┴───────────┴───────────┘
Привет, спасибо за ваш вклад. Ваше первое решение df.select([pl.col('x')+10, pl.col('y')+20, pl.col('z')+30])
уже было опубликовано в моем вопросе. Меня не удовлетворяет «простота» использования поляр, особенно когда массивы становятся больше. Второе решение вполне работоспособно, но, на мой взгляд, оно не использует простой выбор полярных выражений с помощью регулярных выражений. Но спасибо за ваш ответ, но, к сожалению, я не думаю, что это действительно решает проблему python-polars, но это хороший обходной путь.
Я увидел на короткое время ответ, который искал, но комментарий удален.
Решение состояло в том, чтобы вернуть кортеж:
df.select(
pl.col(r'^[x|y|z]$')
).apply(
# lambda x: np.array(x) - arr # old code
lambda x: tuple(np.array(x) - arr) # new code
)
Ага, извините, я запутался и перепроверил результат :(
хорошо, это работает для варианта select
. Не работает с with_columns
например: df.with_columns( pl.col('^[x|y|z]$').apply(lambda x: tuple(np.array(x) - arr)) )
не получилось...
Да. И мне не нравится решение (поэтому я только добавил комментарий).
apply
будет медленнее, вы, вероятно, захотите избежать этого решения, если сможете
@ignoring_gravity медленнее чего? медленнее, чем выбирать и вычитать его для каждого столбца, как вы показали в своем ответе? Я использую этот метод в данный момент, чтобы добиться цели.
да, верно - Дин МакГрегор очень хорошо объясняет, почему вы, вероятно, хотите избежать apply
, если можете
В этом вопросе происходит несколько вещей.
Во-первых, вы действительно не хотите использовать apply, если вы не делаете что-то, что является пользовательской функцией Python.
выражение применения передает элементы столбца функции python. Обратите внимание, что вы сейчас используете python, это будет медленно.
На самом деле нет полярного способа делать то, что вы хотите. Когда polars увидит pl.col(r'^[x|y|z]$').expr
, он идентифицирует каждый столбец, который соответствует регулярному выражению, а затем будет поток, выполняющий работу остальной части выражения. Выражение не знает, в каком порядке оно было. Он знает только, каковы его данные и что он должен делать. Поэтому вы ничего не можете добавить в expr
, чтобы он знал, к какому элементу массива обращаться.
Чтобы получить то, что вы хотите, вам нужно сделать что-то вроде @ignoring_gravity, но вы можете использовать модуль re
.
import re
df.select(pl.col(col)-arr[i]
for i, col in enumerate(filter(re.compile(r'^[x|y|z]$').match, df.columns)))
Другой вариант, который позволяет избежать импорта re
:
res = df.select(
pl.col(col) - c
for col, c in zip(df.select(pl.col(r'^[x|y|z]$')).columns, arr)
)
Это немного медленнее для очень маленьких кадров данных (я думаю, потому что в этом случае преобладает скорость регулярных выражений), но одинаково быстро для больших.
В вашем почтовом индексе мне интересно, будет ли выполнение df.filter(False).select(pl.col(r'^[x|y|z]$')).columns
улучшением производительности, поскольку оно не позволит ему возвращать строки внутри (возможно, это все равно происходит, я не уверен). В любом случае мне нравится этот трюк. Вы также можете сделать pl.DataFrame([x-y for x,y in zip(df.select(pl.col(r'^[x|y|z]$')), arr)])
Измените лямбда-функцию на
lambda x: tuple(np.array(x) - arr)
и повторите попытку.