Преобразование DataFrame в Python-пандах с помощью groupby, pivot и transpose - PullRequest
0 голосов
/ 14 ноября 2018

У меня есть фрейм данных с двумя столбцами: date и bill_id.Диапазон дат в столбце дат - один год с 01-01-2017 по 30-12-2017.Есть 1000 уникальных bill_ids.Каждый bill_id может встречаться хотя бы один раз в столбце bill_id.В результате получается DataFrame размером: 2 столбца, 1000000 строк ...

     dt   |bill_id

01-01-2017 bill_1
01-01-2017 bill_2
02-01-2017 bill_1
02-01-2017 bill_3
03-01-2017 bill_4
03-01-2017 bill_4

, поэтому некоторые name_ids могут встречаться в определенный день, а другие нет.

Чего я хочу добиться, так этофрейм данных в таком формате, чтобы все уникальные bill_ids были столбцами, все уникальные даты - строками, и каждый bill_id имеет либо 0, либо 1, либо 2 для соответствующего значения дня, где 0 = еще не появилось в эту дату, 1 появилось в эту дату, 2 -не появляются в эту дату, но существовали раньше, например,

, если bill_id существовал 02-01-2017, то он имел бы 0 01-01-2017, 1 02-01-2017 и 2 03-01-2017 и 2 во все дни подряд.

Я сделал это в несколько шагов, но код не масштабируется больше, поскольку он медленный:

def map_values(row, df_z, c):
    subs = df_z[[c, 'bill_id', 'date']].loc[df_z['date'] == row['dt']]
    if c not in subs['bill_id']:
        row[c] = max(subs[c].tolist())
    else:
        val = df_z[c].loc[(df_z['date'] == row['dt']) & (df_z['bill_id'] == c)].values
        assert len(val) == 1
        row[c] = val[0]
    return row


def map_to_one(x):
    bills_x = x['bill_id'].tolist()

    for b in bills_x:
        try:
            x[b].loc[x['bill_id'] == b] = 1
        except:
            pass
    return x


def replace_val(df_groupped, col):
    mask = df_groupped.loc[df_groupped['bill_id'] == col].index[df_groupped[col].loc[df_groupped['bill_id'] == col] == 1]

    min_dt = df_groupped.iloc[min(mask)]['date']
    max_dt = df_groupped.iloc[max(mask)]['date']

    df_groupped[col].loc[(df_groupped['date'] < min_dt)] = 0
    df_groupped[col].loc[(df_groupped['date'] >= min_dt) & (df_groupped['date'] <= max_dt)] = 1
    df_groupped[col].loc[(df_groupped['date'] > max_dt)] = 2
    return df_groupped


def reduce_cols(row):
    col_id = row['bill_id']
    row['val'] = row[col_id]
    return row


df = df.sort_values(by='date')
df = df[pd.notnull(df['bill_id'])]
bills = list(set(df['bill_id'].tolist()))

for col in bills:
    df[col] = 9

df_groupped = df.groupby('date')
df_groupped = df_groupped.apply(lambda x: map_to_one(x))
df_groupped = df_groupped.reset_index()
df_groupped.to_csv('groupped_in.csv', index=False)
df_groupped = pd.read_csv('groupped_in.csv')

for col in bills:
    df_groupped = replace_val(df_groupped, col)

df_groupped = df_groupped.apply(lambda row: reduce_cols(row), axis=1)
df_groupped.to_csv('out.csv', index=False)

cols = [x for x in df_groupped.columns if x not in ['index', 'date', 'bill_id', 'val']]
col_dt = sorted(list(set(df_groupped['date'].tolist())))
dd = {x:[0]*len(col_dt) for x in cols}
dd['dt'] = col_dt
df_mapped = pd.DataFrame(data=dd).set_index('dt').reset_index()

for c in cols:
    counter += 1
    df_mapped = df_mapped.apply(lambda row: map_values(row, df_groupped[[c, 'bill_id', 'date']], c), axis=1)

РЕДАКТИРОВАТЬ:

ответ от Джо в порядке, но я решил пойти вместо этого с другим вариантом:

  1. get date.min () и date.max ()
  2. df_groupped = groupby bill_id
  3. Функция применения df_groupped, в которой я проверяю date_x.min () и date_x.max () для каждой группы и сравниваюdate.min () с date_x.min () и date.max () с date_x.max (), и таким образом я знаю, где 0, 1 и 2 :)

1 Ответ

0 голосов
/ 14 ноября 2018

Надеюсь, я понял, какой ваш желаемый результат.

Сначала сделайте crosstab:

df1 = pd.crosstab(df['dt'],df['bill_id'])

Выход:

    bill_id     bill_1  bill_2  bill_3  bill_4
dt                                        
01-01-2017       1       1       0       0
02-01-2017       1       0       1       0
03-01-2017       0       0       0       2

С этого момента вы начинаете изменять df следующим образом: Создайте копию, которую вы будете использовать в качестве маски

df2 = df1.copy()

Заменить 0 после 1 (или другие значения> 1):

for col in df2.columns:
    df2[col] = df2[col].replace(to_replace=0, method='ffill')

    bill_id     bill_1  bill_2  bill_3  bill_4
dt                                        
01-01-2017       1       1       0       0
02-01-2017       1       1       1       0
03-01-2017       1       1       1       2

Теперь вычтите 2 df:

df3 = df1-df2

Это измененные значения:

    bill_id     bill_1  bill_2  bill_3  bill_4
dt                                        
01-01-2017       0       0       0       0
02-01-2017       0      -1       0       0
03-01-2017      -1      -1      -1       0

заменить эти значения на 2:

for col in df3.columns:
    df3[col] = df3[col].replace(-1, 2)

Вернитесь к первому df1 и измените значения больше 1 на 1:

for col in df1.columns:
    df1[col] = df1[col].apply(lambda x: x if x < 2 else 1)

и в итоге вы суммируете этот последний df с df3:

df_add = df1.add(df3, fill_value=0)

Выход:

    bill_id     bill_1  bill_2  bill_3  bill_4
dt                                        
01-01-2017       1       1       0       0
02-01-2017       1       2       1       0
03-01-2017       2       2       2       1

Для завершения замените отрицательные значения:

for col in df_add.columns:
    df_add[col] = df_add[col].apply(lambda x: 2 if x < 0 else x)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...