Поляры вычитают массив numpy 1xn из n столбцов

Я борюсь с поляками. У меня есть фрейм данных и массив 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]
)

Измените лямбда-функцию на lambda x: tuple(np.array(x) - arr) и повторите попытку.

Timus 22.02.2023 13:34

Нет, это не сработало, он вычел arr[0] из всех столбцов.

3dSpatialUser 22.02.2023 13:51
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
89
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы можете сопоставить вывод 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, но это хороший обходной путь.

3dSpatialUser 22.02.2023 11:33

Я увидел на короткое время ответ, который искал, но комментарий удален.

Решение состояло в том, чтобы вернуть кортеж:

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
)

Ага, извините, я запутался и перепроверил результат :(

Timus 22.02.2023 13:34

хорошо, это работает для варианта select. Не работает с with_columns например: df.with_columns( pl.col('^[x|y|z]$').apply(lambda x: tuple(np.array(x) - arr)) ) не получилось...

3dSpatialUser 22.02.2023 13:39

Да. И мне не нравится решение (поэтому я только добавил комментарий).

Timus 22.02.2023 13:42
apply будет медленнее, вы, вероятно, захотите избежать этого решения, если сможете
ignoring_gravity 22.02.2023 13:51

@ignoring_gravity медленнее чего? медленнее, чем выбирать и вычитать его для каждого столбца, как вы показали в своем ответе? Я использую этот метод в данный момент, чтобы добиться цели.

3dSpatialUser 22.02.2023 13:58

да, верно - Дин МакГрегор очень хорошо объясняет, почему вы, вероятно, хотите избежать apply, если можете

ignoring_gravity 22.02.2023 14:51
Ответ принят как подходящий

В этом вопросе происходит несколько вещей.

Во-первых, вы действительно не хотите использовать 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)])

Dean MacGregor 23.02.2023 13:14

Другие вопросы по теме