Сортировка pandas кадра данных по двум столбцам с инкрементным инвариантом - PullRequest
1 голос
/ 23 апреля 2020

У меня есть кадр данных эпизодов imdb, который включает название шоу, номер сезона, номер серии и рейтинг эпизодов. Я хочу отсортировать этот кадр данных по рейтингу, но с учетом того, что более поздний эпизод ДОЛЖЕН быть после более раннего эпизода для определенного шоу, даже если его рейтинг выше.

Вещи, которые я пробовал:

  • Сортировка по рейтингу, номеру сезона, номеру эпизода (различные комбинации)
  • Создание абсолютного номера эпизода на основе номера сезона и эпизода номер (например, 0, 1, 3, он же S01E01, S01E02, S01E03) и сортировка с этим

Мне удалось придумать функцию, которая делает то, что я хочу, и код ниже. Хотя он работает, он медленный, очень мутационный и плохо масштабируется. Я хочу найти лучший способ сделать это, с pandas.

def max_rating_by_episode_in_order(df: pd.DataFrame) -> pd.DataFrame:
    new_df = pd.DataFrame()
    copy_df = df.copy().sort_values(["showName", "seasonNumber", "episodeNumber"])
    while len(copy_df) > 0:
        next_highest = (
            copy_df.reset_index()
            .groupby("showName")
            .first()
            .sort_values("rating", ascending=False)
            .head(1)
            .reset_index()
        )
        new_df = pd.concat([new_df, next_highest], ignore_index=True)
        copy_df = copy_df.drop(next_highest["index"].values[0])
    return new_df

или без него. Вывод должен выглядеть примерно так:

showName        seasonNumber  episodeNumber rating
2 Broke Girls   1             1             7.5
'Til Death      1             1             7.4
'Til Death      1             2             7.5
21 Jump Street  1             1             7.4
2 Broke Girls   1             2             7.3
2 Broke Girls   1             3             7.3
2 Broke Girls   1             4             7.3
21 Jump Street  1             2             7.3
21 Jump Street  1             3             7.6
21 Jump Street  1             4             7.5
'Til Death      1             3             7.2
'Til Death      1             4             7.5
'Til Death      1             5             7.6
'Til Death      1             6             7.6
'Til Death      1             7             7.5
'Til Death      1             8             7.6
'Til Death      1             9             7.7
'Til Death      1             10            7.4
'Til Death      1             11            7.4
'Til Death      1             12            7.6
'Til Death      1             13            7.5
'Til Death      1             14            7.6
'Til Death      1             15            7.5
'Til Death      1             16            7.8
2 Broke Girls   1             5             7.2
2 Broke Girls   1             6             7.2

Ответы [ 2 ]

1 голос
/ 24 апреля 2020

Большая заслуга @jcaliz выше, я предложил следующую функцию, которая отлично работает даже на очень больших наборах данных. Он по-прежнему использует итеративный подход, который может быть улучшен, но куча действительно ускоряет процесс.

import pandas as pd
import heapq

def improved_max_rating_by_episode_in_order(in_df: pd.DataFrame) -> pd.DataFrame:
    df = (
        in_df.copy()
        .reset_index()
        .sort_values(["showName", "seasonNumber", "episodeNumber"])
    )
    # Need to simulate max heap by negating all the ratings to use with a min heap
    df["rating"] = df["rating"] * -1
    gb = df.groupby(["showName"])
    groups = {k: v for k, v in gb}
    sort_indexes = []
    heap = []
    for x in [
        tuple(k.values())
        for k in gb.first()
        .reset_index()[["rating", "showName", "index"]]
        .to_dict("records")
    ]:
        heapq.heappush(heap, x)
    heapq.heapify(heap)
    for _ in range(df.shape[0]):
        rating, showName, index = heapq.heappop(heap)
        sort_indexes.append(index)
        groups[showName] = groups[showName].iloc[1:]
        if groups[showName].shape[0] == 0:
            del groups[showName]
            continue
        heapq.heappush(
            heap,
            tuple(
                groups[showName]
                .head(1)[["rating", "showName", "index"]]
                .to_dict("records")[0]
                .values()
            ),
        )
    return in_df.loc[sort_indexes]
0 голосов
/ 23 апреля 2020

Я использовал концепцию сортировки слиянием и попытался реализовать другой код, так как этот случай не простой, то, что я делаю, это groupby показывается, затем сортируйте каждую группу по season, episode, и, наконец, сравните, какая группа имеет самый высокий рейтинг в первой строке, возьмите строку и удалите и повторяйте, пока все группы не станут пустыми:

df.sort_values(['showName', 'seasonNumber', 'episodeNumber'], inplace=True)

def other_method(df):
    groups = df.groupby(['showName'])
    groups = {k: v for k, v in groups}

    sort_indexes = list()
    keys = [x for x in groups]
    for i in range(df.shape[0]):
        values = np.array([groups[x].iat[0, 3] for x in groups])
        max_value = values.argmax()

        sort_indexes.append(groups[keys[max_value]].index.values[0])
        groups[keys[max_value]] = groups[keys[max_value]].iloc[1:]

        if groups[keys[max_value]].shape[0] == 0:
            del groups[keys[max_value]]
            keys.remove(keys[max_value])

    return df.loc[sort_indexes].copy()

Я провел некоторое сравнение с max_rating_by_episode_in_order, и вот результаты:

times_1 = []
for i in range(10):
    now = datetime.now()
    df_2 = max_rating_by_episode_in_order(df)
    times_1.append(datetime.now() - now)


times_2 = []
for i in range(10):
    now = datetime.now()
    df_2 = other_method(df)
    times_2.append(datetime.now() - now)

pd.to_timedelta(times_1).mean() # 00:00:00.245583
pd.to_timedelta(times_2).mean() # 00:00:00.022659

Я почти уверен, что есть более эффективные способы реализации этой сложной сортировки.

...