Руководящие документы по организации первичной медико-социальной помощи населению предписывают проводить сравнительный анализ численности населения по территориальным участкам (норматив численности населения на терапевтическом участке - 1700 взрослых, на педиатрическом участке - 800 детей, на акушерско-гинекологическом участке - 3300 женщин в возрасте 15 лет и старше и т.д.) .
Оценку численности населения по субъектам РФ Росстат публикует ежегодно на 1 января текущего года. Для крупных городов территории обслуживания населения медицинскими организациями часто не совпадают с адресно административным делением и распределение населения по зонам ответственности медицинской организации становится скорее творчеством нежели технологической процедурой. Вопрос как декомпозировать данные из бюллетеня Росстата до медицинского участка для меня остается нерешенным.
Мы пойдем другим путем. Данные о населении мы можем получить из медицинской информационной системы (МИС). База МИС обогащается на регулярной основе данными страховых компаний о застрахованных лицах по программе обязательного медицинского страхования (ОМС).
Для работы нам понадобится обезличенная выгрузка из МИС, содержащая данные по пациентам: пол, дату рождения, адрес регистрации, адрес фактического места жительства, данные медицинской организации и номера участка по терапевтическому или педиатрическому профилю. Я загрузил ее в pandas.dataframe.
Рассчитаем возраст пациентов на текущую дату и отсортируем по возрастанию. В нашем случае, в МИС используется несколько форматов хранения даты рождения, учитываем это при написании функции.
def calculate_age(birth_date):
format_str=["%d.%m.%Y", "%d.%m.%Y %H:%M:%S", "%Y-%m-%d %H:%M:%S"]
today = date.today()
i = 0
while (i < len(format_str)):
try:
birth_date = datetime.datetime.strptime(birth_date, format_str[i])
except:
i += 1
try:
age = today.year - birth_date.year
full_year_passed = (today.month, today.day) < (birth_date.month, birth_date.day)
if not full_year_passed:
age -= 1
#в каждой БД есть свой ветеран Куликовской битвы
#if age > 100:
# print(birth_date)
except:
age = -1
return age
df['age'] = df['birthdate'].apply(calculate_age)
df = df.sort_values(by='age', ascending=True)
Методы pandas позволяют агрегировать и группировать данные. Сделаем группировку населения по возрасту для часто использующих запросов информации и построим график
#Группировка дети/взрослые и т.д методами Pandas
df['age_lvls_child_adults'] = pd.cut(df['age'],bins=[-999,0,18,199],
labels=['Неуказан','0-17','18+'],right=False)
df['age_lvls_foms'] = pd.cut(df['age'],bins=[-999,0,5,18,60,199],
labels=['Неуказан','0-4','5-17','18-59', '60+'],right=False)
df['age_lvls_stat']=pd.cut(df['age'],
bins=[-999,0,
5,10,15,20,25,
30,35,40,45,50,
55,60,65,70,199],
labels=['Неуказан',
'0-4','5-9','10-14','15-19','20-24',
'25-29','30-34','35-39','40-44','45-49',
'50-54','55-59','60-64','65-69','70+'],right=False)
df['age_lvls_death']=pd.cut(df['age'],
bins=[-999,0,1,2,3,4,
5,10,15,20,25,
30,35,40,45,50,
55,60,65,70,75,
80,85,199],
labels=['Неуказан','0..','1','2','3',
'4','5-9','10-14','15-19','20-24',
'25-29','30-34','35-39','40-44','45-49',
'50-54','55-59','60-64','65-69','70-74',
'75-79','80-84','85+'],right=False)
df_age_lvls_stat = df.groupby(by=['age_lvls_death'],
as_index=False)[['ID','age']].count()
fig_stat = px.bar(df_age_lvls_stat, x='age_lvls_death', y='ID',
labels={'age_lvls_death':'Возрастная группа','ID':'Количество человек'}
)
fig_stat.show()
Разделим данные по полу и построим график:
#Полово-возрастной состав, расчетная база для группировки “Смертность населения”
df_gb = df.groupby(by=['age_lvls_death','gender'],
as_index=False)[['id']].agg(['count']) .reset_index()
fig_gender = px.bar(df_gb, x='age_lvls_death', y='id',
color='gender',
color_discrete_map={
'женский': 'red',
'мужской': 'blue'
},
labels={'age_lvls_death':'Возрастная группа',
'gender':'Пол','id':'Количество человек'},
barmode="group",
height=400
)
fig_gender.show()
Для более сложных запросов, учитывающих пол, разный возраст для выхода на пенсию, я использовал метод select пакета numpy:
male = 'мужской'
female = 'женский'
#Примечание: мужчины 16 61 года, женщины 16 56 лет.
#Трудоспособный возраст в соответствии с приказом Росстата:
#2021 год 16 60 лет для мужчин и 16 55 лет для женщин;
#2022 года 16 61 года для мужчин и 16 56 лет для женщин.
conditions_mzrf = [
df['age'] < 0,
df['age'].between(0, 15),
(df['age'].between(16, 60)) & (df['SEX'] == male),
(df['age'].between(16, 55)) & (df['SEX'] == female),
(df['age'] >= 61) & (df['SEX'] == male),
(df['age'] >= 56) & (df['SEX'] == female)
]
choices_mzrf = [' Неуказан','0-15','16-55/16-60','16-55/16-60','56+/61+','56+/61+']
df['age_lvls_trud'] = np.select(conditions_mzrf, choices_mzrf)
fig_trud = px.bar(df_trud.sort_values('age_lvls_trud', ascending=True),
x='age_lvls_trud', y='ID',
color='SEX', #включает легенду из столбца ДФ
color_discrete_map={'женский': 'red',
'мужской': 'blue'},
labels={'age_lvls_trud':'Возрастная группа','SEX':'Пол',
'ID':'Количество человек'},
barmode="group",
text_auto=True,
height=400,
color_discrete_sequence=px.colors.diverging.Spectral
)
fig_trud.update_traces(textfont_size=14, textangle=-90, textposition="inside",
cliponaxis=True)
fig_trud.update_layout(uniformtext_minsize=8, uniformtext_mode='hide')
fig_trud.show()
Итак, имея в наличии актуальную базу населения из медицинской информационной сситемы можно за несколько минут составить по ней статистический бюллетень на текущую дату для области, района области или города, медицинской организации, медицинского участка или дома. Также несложно организовать сравнение своих данных с официальной информацией Росстата.