Создайте новый столбец с первым значением, соответствующим условию.Python

Программы на Python
Anonymous
Создайте новый столбец с первым значением, соответствующим условию.

Сообщение Anonymous »

У меня есть такой Dataframe:
import polars as pl

df = pl.from_repr("""
┌──────┬───────┐
│ Time ┆ Value │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞══════╪═══════╡
│ 1 ┆ 100 │
│ 2 ┆ 75 │
│ 3 ┆ 70 │
│ 4 ┆ 105 │
│ 5 ┆ 140 │
│ 6 ┆ 220 │
│ 7 ┆ 65 │
│ 8 ┆ 180 │
│ 9 ┆ 150 │
└──────┴───────┘
""")

(Обратите внимание, что он сортируется по времени)
Мне нужно создать новый столбец с именем NewColumn, что приведет к следующему разу, где значение меньше текущего значения.
EDIT: важно указать, что мой исходный набор данных содержит более 10 миллионов строк, и хотя исходное требование соответствует приведенному выше, справедливо сказать, что некоторые операции могут очень быстро превысить ОЗУ. Чтобы сбалансировать это, было бы приемлемо ввести предварительный предел для проверки исходного состояния. Например, было бы приемлемо вывести следующее время, где значение ниже текущего значения до следующих 100 раз
Примерно так:
| Time | Value | NewColumn |
| 1 | 100 | 2 | >> next Time with Value lower than 100
| 2 | 75 | 3 | >> next Time with Value lower than 75
| 3 | 70 | 7 | >> next Time with Value lower than 70
| 4 | 105 | 7 | >> next Time with Value lower than 105
| 5 | 140 | 7 | >> next Time with Value lower than 140
| 6 | 220 | 7 | >> next Time with Value lower than 220
| 7 | 65 | null | >> next Time with Value lower than 65
| 8 | 180 | 9 | >> next Time with Value lower than 180
| 9 | 150 | null | >> next Time with Value lower than 150

До сих пор я пытался создать новый временный столбец, который будет содержать фрагмент значения от следующей до последней строки, например:< /p>
| Time | Value | Slice_of_Value |
| 1 | 100 | [75, 70, … 150] |
| 2 | 75 | [70, 105, … 150] |
| 3 | 70 | [105, 140, … 150] |
| 4 | 105 | [140, 220, … 150] |
| 5 | 140 | [220, 65, … 150] |
| 6 | 220 | [65, 180, 150] |
| 7 | 65 | [180, 150] |
| 8 | 180 | [150] |
| 9 | 150 | [] |

Затем попробуйте определить позицию первого совпадения, удовлетворяющего условию: «ниже значения столбца». В результате получится что-то вроде этого:
| Time | Value | Slice_of_Value | Position |
| 1 | 100 | [75, 70, … 150] | 0 |
| 2 | 75 | [70, 105, … 150] | 0 |
| 3 | 70 | [105, 140, … 150] | 3 |
| 4 | 105 | [140, 220, … 150] | 2 |
| 5 | 140 | [220, 65, … 150] | 1 |
| 6 | 220 | [65, 180, 150] | 0 |
| 7 | 65 | [180, 150] | null |
| 8 | 180 | [150] | 0 |
| 9 | 150 | [] | null |

Теперь я задел несколько проблем:
Шаг 1: Slice_of_Value
Чтобы получить Slice_of_Value столбец, вот что я попробовал сначала:
df = df.with_columns(
pl.col("Value").slice(pl.col("Time"), 9).implode().alias("Slice_of_Value")
)

но, похоже, невозможно использовать pl.col("") как часть .slice()... Поэтому я прибегнул к вместо этого сделайте что-нибудь вроде этого:
df = df.with_columns(
pl.col("Value").shift(-i).alias(f"lag_{i}") for i in range(1, 9)
).with_columns(
pl.concat_list([f"lag_{i}" for i in range(1, 9)]).alias("Slice_of_Value")
)

Пока все хорошо.
Шаг 2: Позиция
df = df.with_columns(
pl.col("Slice_of_Value")
.list.eval(pl.arg_where(pl.element() < pl.col("Value")))
.list.eval(pl.element().first())
.list.eval(pl.element().drop_nulls())
.explode()
.add(pl.col("Time") + 1)
.alias("NewColumn")
)

К сожалению, этот фрагмент кода не работает, поскольку именованные столбцы не разрешены в list.eval ... Так что сейчас я как бы упираюсь в стену. Я не знаю, неправильный ли мой подход или я что-то упустил из документации.
Любая помощь или предложение очень ценятся :)
РЕДАКТИРОВАТЬ: ЭТАЛОННЫЕ РЕЗУЛЬТАТЫ
На данный момент я попробовал 4 решения на своем реальном наборе данных. Вот мои тесты:
РЕШЕНИЕ 1: 0,83 с для 10 млн строк
lookahead=10
df = dfSource.with_columns(
pl.when(pl.col("Value").shift(-i)
.then(pl.col("Time").shift(-i)).alias(f"time_{i}") for i in range(1, lookahead+1)
).with_columns(
NewColumn=pl.coalesce(pl.col(f"time_{i}") for i in range(1,lookahead+1))
).drop(f"time_{i}" for i in range(1,lookahead+1)).collect()

РЕШЕНИЕ 2: 3,41 с для 10 млн строк
df.rolling("Time", period=f"{df.height}i", offset="0i").agg(
x=pl.arg_where(pl.col("Value") < pl.col("Value").first()).first() - 1
)

Выдал неверный результат, поэтому я преобразовал его так:
df = df.group_by_dynamic(
"Time",
every="1i",
period="10i",
include_boundaries=False,
closed="left",
).agg(
pl.col("Value").alias("Slices")
).select(
pl.col("Time"),
pl.col("Slices")
.list.eval(pl.arg_where(pl.element() < pl.element().first()))
.list.first()
.add(pl.col("Time"))
.alias("NewColumn"),
).collect()

РЕШЕНИЕ 3: (превышение ОЗУ)
df.with_columns(position=pl.col("Value").implode()).with_columns(
next_time=pl.col("position")
.list.gather(pl.int_ranges(pl.col("Time") - 1, df.height))
.list.eval(pl.arg_where(pl.element() < pl.element().first()))
.list.first()
+ pl.col('Time') # +1 and -1 cancel out here.
)

Превышение ОЗУ из-за pl.col("Value").implode(), поскольку это означает транспонирование 10 миллионов строк в списки по 10 миллионов элементов...Поэтому я принял РЕШЕНИЕ 1, которое дало самые быстрые результаты в реальной ситуации, а также самый чистый код, ИМХО (без списков, более кратко, нет необходимости выполнять дальнейшие объединения...).
Наконец, вот еще несколько показателей после увеличения размера просмотра.
Lookahead Size | SOLUTION 1 Time | SOLUTION 2 Time |
10 | 0.83s | 3.41s |
20 | 1.24s | 3.83s |
50 | 2.24s | 5.34s |
100 | 4.45s | 8.10s |
200 | 8.58s | 17.86s |
500 | 20.24s | 66.93s |
1000 | 70.63s | 108.12s |


Подробнее здесь: https://stackoverflow.com/questions/764 ... -condition

Вернуться в «Python»