Эффективный Pandas складской расчет - PullRequest
1 голос
/ 10 июля 2020

У меня есть набор данных дат рождения и смерти, например:

d1 = {'Birth_date': [1800,1810,1802,1804], 'Death_date': [1805, 1880,1854,1832]}
pd.DataFrame(data=d1)

   Birth_date  Death_date
0        1800        1805
1        1810        1880
2        1802        1854
3        1804        1832

Я хочу вычислить:

  • Количество живых особей за данный год в данный возраст ( например, число лиц в возрасте 18 лет, живущих в 1825 г. )
  • Количество смертей за данный год в данном возрасте ( например, число 18 лет, люди, которые умерли в 1825 году )

Теоретически результат будет выглядеть так:

   Date Number ind. aged 1 Number ind. aged 2 Number ind. aged k
0  1800                 .                 .                 .
1  1801                 .                 .                 .
2  1802                 .                 .                 .
3  1803                 .                 .                 .

и

   Date Number death aged 1 Number death aged 2 Number death aged k
0  1800                 .                 .                 .
1  1801                 .                 .                 .
2  1802                 .                 .                 .
3  1803                 .                 .                 .

Я не Я не вижу простого способа вычислить это. Кто-нибудь сталкивался с подобным вопросом?

Ответы [ 2 ]

0 голосов
/ 10 июля 2020

Q1: Количество живых особей за данный год в данном возрасте и году:

Данный фрейм данных d1 как в вопросе выше:

d2 = \
pd.concat(\
    d1.apply(\
        lambda x: pd.DataFrame(\
        {'id': x.name,\
         'year': range(x['Birth_date'], x['Death_date']+1),\
         'age': range(x['Birth_date'], x['Death_date']+1)-x['Birth_date']}),\
     axis = 1).to_list())        

d2 выглядит так:

    id  year  age
0    0  1800    0
1    0  1801    1
2    0  1802    2
3    0  1803    3
4    0  1804    4
..  ..   ...  ...
24   3  1828   24
25   3  1829   25
26   3  1830   26
27   3  1831   27
28   3  1832   28

[159 rows x 3 columns]

id представляет отдельного человека, выведенного из индекса d1. Затем просто поверните d2 подсчет живых людей в данном возрасте и году:

nlvng = pd.pivot_table(d2, columns = 'age', index = 'year', values = 'id', aggfunc = 'count', fill_value=0)

набор результатов:

age   0   1   2   3   4   5   6   7   8   ...  62  63  64  65  66  67  68  69  70
year                                      ...                                    
1800   1   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1801   0   1   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1802   1   0   1   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1803   0   1   0   1   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1804   1   0   1   0   1   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
  ..  ..  ..  ..  ..  ..  ..  ..  ..  ...  ..  ..  ..  ..  ..  ..  ..  ..  ..
1876   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   1   0   0   0   0
1877   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   1   0   0   0
1878   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   1   0   0
1879   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   1   0
1880   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   1

[81 rows x 71 columns]

Q2: количество смертей за данный год в заданный возраст:

Здесь, используя ранее вычисленное d2, объедините его с d1 на d1.index и Death_date:

d3 = d2.merge(d1, left_on = ['id','year'], right_on = [d1.index,'Death_date'], how = 'outer')

ndeaths = pd.pivot_table(d3, columns = 'age', index = 'year', values = 'Death_date', aggfunc = 'count', fill_value=0)

вывод:

age   0   1   2   3   4   5   6   7   8   ...  62  63  64  65  66  67  68  69  70
year                                      ...                                    
1800   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1801   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1802   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1803   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1804   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
  ..  ..  ..  ..  ..  ..  ..  ..  ..  ...  ..  ..  ..  ..  ..  ..  ..  ..  ..
1876   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1877   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1878   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1879   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   0
1880   0   0   0   0   0   0   0   0   0  ...   0   0   0   0   0   0   0   0   1

[81 rows x 71 columns]
0 голосов
/ 10 июля 2020

Изменить : Извините, во-первых, совершенно неправильный ответ.

Теперь я думаю, что это может приблизиться к тому, что было запрошено. Возможно, это не самое эффективное решение - может быть, кто-то другой найдет что-то получше?

Решение сначала создает искусственный df со всеми возможными годами и столбцом для каждого человека. Затем он вычисляет возраст каждого человека в каждом году и, наконец, подсчитывает возможные значения за год и возраст человека.

import pandas as pd


def ind_age(x, min_val, max_val):
    if min_val <= x < max_val:
        return x - min_val + 1  # a person has no age 0
    else:
        return 0

# init df
d1 = {'Birth_date': [1800, 1810, 1802, 1804], 'Death_date': [1805, 1880, 1854, 1832]}
d1 = pd.DataFrame(data=d1)

# min and max years to init df
min_year = d1[['Birth_date', 'Death_date']].min().min()
max_year = d1[['Birth_date', 'Death_date']].max().max()

# get all years possible as a column
df_years = pd.DataFrame(range(min_year, max_year + 1))
df_years.columns = ['years']

# transpose to prepare left join
# the left join will make it possible to insert custom values
# for each year and person
d1 = d1.transpose()

for colname in d1.columns:
    # calculates the age of a person in each year
    df_years = pd.merge(left=df_years, right=pd.DataFrame(d1[colname]), how='left', left_on='years', right_on=colname)

for col in df_years.columns[1:]:
    col_min = df_years[col].min()
    col_max = df_years[col].max()
    df_years[col] = df_years['years'].apply(lambda x: ind_age(x, col_min, col_max))

df_years.set_index('years', inplace=True)

result = df_years.apply(pd.Series.value_counts, axis=1).fillna(0)

Результат выглядит так:

       0.0   1.0   2.0   3.0   4.0   5.0   ...  65.0  66.0  67.0  68.0  69.0  70.0
years                                      ...                                    
1800    3.0   1.0   0.0   0.0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
1801    3.0   0.0   1.0   0.0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
1802    2.0   1.0   0.0   1.0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
1803    2.0   0.0   1.0   0.0   1.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
1804    1.0   1.0   0.0   1.0   0.0   1.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
     ...   ...   ...   ...   ...   ...  ...   ...   ...   ...   ...   ...   ...
1876    3.0   0.0   0.0   0.0   0.0   0.0  ...   0.0   0.0   1.0   0.0   0.0   0.0
1877    3.0   0.0   0.0   0.0   0.0   0.0  ...   0.0   0.0   0.0   1.0   0.0   0.0
1878    3.0   0.0   0.0   0.0   0.0   0.0  ...   0.0   0.0   0.0   0.0   1.0   0.0
1879    3.0   0.0   0.0   0.0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   1.0
1880    4.0   0.0   0.0   0.0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
[81 rows x 71 columns]

Для смертей вы можете изменить метод ind_age (), чтобы он возвращал значение только в день смерти (x == max_val) и возвращал соответствующий возраст смерти. Зависит от того, как вы хотите рассчитывать возраст (начиная с 0 или 1).

...