Очистите строковый столбец с беспорядочными данными в числовые значения в полярах

Был указан следующий код:


import polars as pl
import numpy as np

# Set the random seed for reproducibility
np.random.seed(0)

# Define the sample size
n = 35000000

# Define the possible values
values_CRP = ["10", "2", "3", None, "<4", ">5"]
values_CRP2 = ["10", "12", "<5", "NA", ">5", "5"]
values_CRP3 = ["10", "12.3", "<5", "NA", ">5.5", "4"]

# Create the DataFrame
df = pl.DataFrame({
    "CRP": np.random.choice(values_CRP, n, replace=True),
    "CRP2": np.random.choice(values_CRP2, n, replace=True),
    "CRP3": np.random.choice(values_CRP3, n, replace=True)
})

Предположим, что это столбцы трех разных биомаркеров. Я хочу очистить их, захватив числовую часть каждого строкового значения. Если строка не начинается с «<» или «>» («10» должно давать 10), оставьте нули как есть (это означает, что пациент не иметь какие-либо измерения в этот момент времени) и замените значения, начинающиеся с «<» или «>», медианой значений, которые находятся ниже или выше второго элемента соответствующего значения. Например, «<5» следует заменить медианой наблюдений со значением биомаркера ниже 5. За «>5» берем наблюдения выше. Если у нас есть значение «>10000» и нет наблюдений выше 10000, то мы обнуляем его. То же самое с <.

Желаемый результат для минимального примера:


df1 = pl.DataFrame({"Current":["<4","3", "2", None, ">5", "10"],
                   "Goal": [2.5,3,2,None,10,10]})

В идеале, поскольку в реальности у меня 11 столбцов и почти 40 миллионов строк, мне бы хотелось делать как можно больше в ленивом режиме.

Почему в 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
0
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я думаю, вам нужно будет соединить dataframe с самим собой или запустить какой-то подзапрос, и для этих задач я считаю, что DuckDB очень удобен для пользователя:

df = df.with_columns(
    num = pl.col.Current.cast(pl.Float64, strict=False)
)

duckdb.sql("""
    select
        d.Current,
        d.Goal,
        case
            when d.Current[1] == '<' then
                (select median(tt.num) from df as tt where tt.num < try_cast(d.Current[2:] as float))
            when d.Current[1] == '>' then
                (select median(tt.num) from df as tt where tt.num > try_cast(d.Current[2:] as float))
            else d.num
        end as Calc
    from df as d
""").pl()
shape: (6, 3)
┌─────────┬──────┬──────┐
│ Current ┆ Goal ┆ Calc │
│ ---     ┆ ---  ┆ ---  │
│ str     ┆ f64  ┆ f64  │
╞═════════╪══════╪══════╡
│ <4      ┆ 2.5  ┆ 2.5  │
│ 3       ┆ 3.0  ┆ 3.0  │
│ 2       ┆ 2.0  ┆ 2.0  │
│ null    ┆ null ┆ null │
│ >5      ┆ 10.0 ┆ 10.0 │
│ 10      ┆ 10.0 ┆ 10.0 │
└─────────┴──────┴──────┘

Для чистых полярных значений вы, вероятно, могли бы сначала предварительно рассчитать медиану для всех уникальных значений, а затем просто объединить:

df_num = df.select(num = pl.col.Current.cast(pl.Float64, strict=False)).drop_nulls()
df_calc = (
    df
    .filter(pl.col.Current.str.head(1).is_in(["<",">"]))
    .select(
        pl.col.Current,
        oper = pl.col.Current.str.head(1),
        bound = pl.col.Current.str.tail(-1).cast(pl.Float64)
    )
    .unique()
)

df_mapping = (
    df_num
    .join(df_calc, how = "cross")
    .filter(
        ((pl.col.oper == ">") & (pl.col.num > pl.col.bound)) |
        ((pl.col.oper == "<") & (pl.col.num < pl.col.bound))
    )
    .group_by("Current")
    .agg(pl.col.num.median())
)

(
    df
    .join(df_mapping, on = "Current", how = "left")
    .with_columns(pl.col.num.fill_null(pl.col.Current))
)
ape: (6, 3)
┌─────────┬──────┬──────┐
│ Current ┆ Goal ┆ num  │
│ ---     ┆ ---  ┆ ---  │
│ str     ┆ f64  ┆ f64  │
╞═════════╪══════╪══════╡
│ <4      ┆ 2.5  ┆ 2.5  │
│ 3       ┆ 3.0  ┆ 3.0  │
│ 2       ┆ 2.0  ┆ 2.0  │
│ null    ┆ null ┆ null │
│ >5      ┆ 10.0 ┆ 10.0 │
│ 10      ┆ 10.0 ┆ 10.0 │
└─────────┴──────┴──────┘

Если вы хотите расширить его на несколько столбцов, вы, вероятно, можете сделать то же самое, но unpivot() сначала ваш DataFrame, вычислите значения, а затем Pivot() все обратно.

df = pl.DataFrame({
    "CRP1":["<4","3", "2", None, ">5", "10"],
    "CRP2":["1","2", None, "3", "4", "<2"]
})
shape: (6, 2)
┌──────┬──────┐
│ CRP1 ┆ CRP2 │
│ ---  ┆ ---  │
│ str  ┆ str  │
╞══════╪══════╡
│ <4   ┆ 1    │
│ 3    ┆ 2    │
│ 2    ┆ null │
│ null ┆ 3    │
│ >5   ┆ 4    │
│ 10   ┆ <2   │
└──────┴──────┘
df_unpivot = df.with_row_index().unpivot(index = "index")

df_num = (
    df_unpivot
    .select(
        pl.col.variable,
        num = pl.col.value.cast(pl.Float64, strict=False)
    )
    .drop_nulls()
)

df_calc = (
    df_unpivot
    .filter(pl.col.value.str.head(1).is_in(["<",">"]))
    .select(
        pl.col.variable,
        pl.col.value,
        oper = pl.col.value.str.head(1),
        bound = pl.col.value.str.tail(-1).cast(pl.Float64)
    )
    .unique()
)

df_mapping = (
    df_calc
    .join(df_num, on = "variable", how = "inner")
    .filter(
        ((pl.col.oper == ">") & (pl.col.num > pl.col.bound)) |
        ((pl.col.oper == "<") & (pl.col.num < pl.col.bound))
    )
    .group_by("variable","value")
    .agg(pl.col.num.median())
)

(
    df_unpivot
    .join(df_mapping, on=["variable","value"], how = "left")
    .with_columns(pl.col.num.fill_null(pl.col.value).cast(pl.Float64))
    .pivot("variable", index = "index", values=["value","num"])
)
shape: (6, 5)
┌───────┬────────────┬────────────┬──────────┬──────────┐
│ index ┆ value_CRP1 ┆ value_CRP2 ┆ num_CRP1 ┆ num_CRP2 │
│ ---   ┆ ---        ┆ ---        ┆ ---      ┆ ---      │
│ u32   ┆ str        ┆ str        ┆ f64      ┆ f64      │
╞═══════╪════════════╪════════════╪══════════╪══════════╡
│ 0     ┆ <4         ┆ 1          ┆ 2.5      ┆ 1.0      │
│ 1     ┆ 3          ┆ 2          ┆ 3.0      ┆ 2.0      │
│ 2     ┆ 2          ┆ null       ┆ 2.0      ┆ null     │
│ 3     ┆ null       ┆ 3          ┆ null     ┆ 3.0      │
│ 4     ┆ >5         ┆ 4          ┆ 10.0     ┆ 4.0      │
│ 5     ┆ 10         ┆ <2         ┆ 10.0     ┆ 1.0      │
└───────┴────────────┴────────────┴──────────┴──────────┘

Вы используете информацию из pl.col.Goal, чего у нас вообще не будет. Как я уже писал, исходный фрейм данных указан в начале моего вопроса.

Vartholome 01.08.2024 20:53

Я не использую столбец «Цель», я оставил его здесь только для сравнения.

Roman Pekar 01.08.2024 21:12

Извиняюсь, меня смутил просто fill_null в конце. Я собираюсь попробовать это в ближайшее время на своих данных.

Vartholome 01.08.2024 21:22

ах да, моя вина, это должно быть Current конечно, исправлено

Roman Pekar 01.08.2024 21:29

Есть идеи, как применить ваш процесс программирования ко всем переменным, которые требуют этого процесса?

Vartholome 01.08.2024 23:26

Можно запустить его с переменными столбцами, но он, вероятно, будет включать unpivot, который еще больше увеличит размер вашего набора данных, если у вас 11 столбцов, может быть, вы можете просто запустить цикл по этим столбцам, а затем объединить результаты в новый DataFrame?

Roman Pekar 01.08.2024 23:45

Можете ли вы показать свою попытку? Я не знаком с Python и в основном использую R.

Vartholome 02.08.2024 00:07

Давайте продолжим обсуждение в чате.

Vartholome 02.08.2024 09:36

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

Похожие вопросы

Проблема с очисткой веб-страниц с использованием селена и красивого супа
Как сгруппировать части маски в изображении с помощью Python?
Как подсчитать длину строки в Python (если строка содержит экранирование символов)?
Как мне найти максимальное значение динамического окна и минимальное значение ниже него?
Как изменить цвет определенного слова пользователя в «input()»
C-расширение Python с numpy случайно аварийно завершает работу после нескольких вызовов (5–10) из кода Python
Создайте фабрику виджетов в Qt
Почему GEKKO не предоставляет оптимальные команды, хотя выходные данные не соответствуют эталонным?
Java sshtools сгенерировал подпись EDDSA, не совпадающую с сгенерированной подписью пикриптома Python
Как записать атрибуты каждого экземпляра в переменную класса, при этом родительский класс записывает экземпляры всех классов в одну и ту же переменную?