Анализът на последователност от състояния/събития е една доста интересна задача, която намира широко приложение в различни области. Например в търговията при анализ на поведението на клиентите или при изследване на определена бизнес ситуация като продажба или друга дейност, която преминава през различни етапи.
С получените резултати от анализа можем да достигнем до конкретни изводи и да предприемем действия, с които да подпомогнем работата на бизнеса.
Тази тема беше разгледана на нашия семинар от 12 март 2021 – Бизнес и маркетинг с R: Как да анализираме поредица от събития и състояния?
По време на семинара чрез практически пример бяха представени възможностите на пакетите TraMineR и arulesSequences за езика R. В тази статия ще ви покажа в контекста на същия пример как можем да анализираме последователност от състояния/събития само че с езика за програмиране Python.
С какви данни ще работим?
Извадката съдържа 2000 наблюдения с данни от социологическо проучване за семейното състояние. Участниците са на възраст между 15 и 30 години.
Състоянията са кодирани по следния начин:
Код | Състояние | Отделно | Семейство | Деца | Развод |
---|---|---|---|---|---|
0 | Parent (P) | не | не | не | не |
1 | Left (L) | да | не | не | не |
2 | Married (M) | не | да | да/не | не |
3 | Left+Marr (LM) | да | да | не | не |
4 | Childr (C) | не | не | да | не |
5 | Left+Childr (LC) | да | не | да | не |
6 | Left+Marr+Childr (LMC) | да | да | да | не |
7 | Divorced (D) | да/не | да/не | да/не | да |
5 произволни реда от данните:
idhous | sex | birthyr | nat_1_02 | plingu02 | p02r01 | p02r04 | cspfaj | cspmoj | a15 | a16 | a17 | a18 | a19 | a20 | a21 | a22 | a23 | a24 | a25 | a26 | a27 | a28 | a29 | a30 | wp00tbgp | wp00tbgs | yr_cuts | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
327 | 69531 | woman | 1926 | Switzerland | german | Protestant or Reformed Church | about once a month | qualified manual professions | nan | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 1385.64 | 1.22973 | 1919-28 |
386 | nan | woman | 1945 | nan | nan | nan | nan | qualified non-manual professions | nan | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1280.9 | 1.13678 | 1939-48 |
762 | nan | man | 1934 | nan | nan | nan | nan | nan | nan | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 3 | 1764.67 | 1.56612 | 1929-38 |
1501 | 89821 | man | 1911 | Switzerland | nan | nan | nan | unqualified non-manual and manual workers | nan | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 1158.91 | 1.02851 | 1909-18 |
1752 | 59191 | man | 1951 | Switzerland | french | Roman Catholic | once a week | intermediate professions | nan | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 3 | 757.741 | 0.672483 | 1949-58 |
Как изглеждат описателните статистики?
За да добием малко повече представа за това с какви данни разполагаме, ще погледнем описателните статистики (мода, медиана, средна стойност, стандартно отклонение и др.). Те ни позволяват на пръв поглед да видим кои са най-често срещаните стойности в данните.
Първо ще погледнем описателните статистики за числовите променливи в извадката. За тази цел ще използваме метода describe, предоставен от библиотеката за анализ на данни Pandas.
# Извеждане на описателните статистики за числови променливи
df.describe()
idhous | birthyr | a15 | a16 | a17 | a18 | a19 | a20 | a21 | a22 | a23 | a24 | a25 | a26 | a27 | a28 | a29 | a30 | wp00tbgp | wp00tbgs | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 1776 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 | 2000 |
mean | 73908.7 | 1942.53 | 0.014 | 0.052 | 0.08 | 0.154 | 0.278 | 0.502 | 0.776 | 1.13 | 1.486 | 1.844 | 2.324 | 2.716 | 3.065 | 3.4 | 3.66 | 3.888 | 1193.87 | 1.06 |
std | 42736 | 10.46 | 0.118 | 0.222 | 0.305 | 0.554 | 0.808 | 1.135 | 1.417 | 1.732 | 1.951 | 2.103 | 2.243 | 2.317 | 2.338 | 2.349 | 2.32 | 2.279 | 707.323 | 0.628 |
min | 2761 | 1909 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
25% | 36241 | 1935 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 2 | 797.289 | 0.708 |
50% | 72716 | 1944 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0.5 | 1 | 1 | 1 | 2 | 3 | 3 | 3 | 3 | 1007.05 | 0.894 |
75% | 109864 | 1951 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 2 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 1381.11 | 1.226 |
max | 148921 | 1957 | 1 | 1 | 6 | 6 | 6 | 6 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 6793.16 | 6.029 |
Годините на раждане са със стойности от 1909 до 1957. Средната година на раждане е 1943 година. За останалите колони describe не дава особено полезна информация, тъй като колоните с години на участниците в проучването (от а15 до а30) съдържат кодове на състоянията. По-нататък в задачата ще анализираме данните в тях.
За да видим описателните статистики за категорийните променливи, ще използваме отново метода describe, но на параметъра include ще зададем стойността object.
# Извеждане на описателните статистики за категорийни променливи
df.describe(include='object')
sex | nat_1_02 | plingu02 | p02r01 | p02r04 | cspfaj | cspmoj | |
---|---|---|---|---|---|---|---|
count | 2000 | 1775 | 1647 | 1566 | 1566 | 1584 | 552 |
unique | 2 | 21 | 3 | 9 | 10 | 8 | 8 |
top | woman | Switzerland | german | Protestant or Reformed Church | only for family ceremonies | other self-employed | other self-employed |
freq | 1092 | 1647 | 1125 | 711 | 521 | 551 | 258 |
От таблицата става ясно например, че в извадката има най-много хора, които са швейцарци и такива с роден език немски. Също така ако погледнем колоната пол, ще видим, че мъжете и жените са приблизително равномерно разпределени. Жените са 1092 на брой, а останалите 908 са мъже.
Изследване на данните
Ще работим предимно с колоните от а15 до а30, тъй като те съдържат данните за състоянията, които ще анализираме. За няколко визуализации ще използваме и някои от останалите колони – пол (sex), година на раждане (birthyr) и роден език
(plingu02).
Първо ще отделим само тези колони в нов DataFrame.
# Избиране на определени колони от данните
df_seq = df.iloc[:,9:25]
Данните изглеждат по следния начин:
a15 | a16 | a17 | a18 | a19 | a20 | a21 | a22 | a23 | a24 | a25 | a26 | a27 | a28 | a29 | a30 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
203 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
1539 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2 | 2 | 7 | 7 | 7 | 7 | 7 |
1170 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 3 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 |
1367 | 0 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
198 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
Тъй като така с числа е малко по-трудно да разберем за кое състояние точно става въпрос и е нужно да правим справка, ще кодираме стойностите, за да е по-лесна интерпретацията на данните.
# Кодиране на състоянията
df_seq.replace({0:'P', 1:'L', 2:'M', 3:'LM', 4:'C', 5:'LC', 6:'LMC', 7:'D'}, inplace=True)
След кодиране на състоянията данните изглеждат така:
a15 | a16 | a17 | a18 | a19 | a20 | a21 | a22 | a23 | a24 | a25 | a26 | a27 | a28 | a29 | a30 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
203 | P | L | L | L | L | L | L | L | L | L | L | L | L | L | L | L |
1539 | P | P | P | P | P | P | P | P | P | M | M | D | D | D | D | D |
1170 | P | P | P | P | P | P | LM | LM | LM | LM | LMC | LMC | LMC | LMC | LMC | LMC |
1367 | P | P | P | P | P | LMC | LMC | LMC | LMC | LMC | LMC | LMC | LMC | LMC | LMC | LMC |
198 | P | L | L | L | L | L | L | LMC | LMC | LMC | LMC | LMC | LMC | LMC | LMC | LMC |
Например нека погледнем ред 1170 от таблицата. При него до 20 годишна възраст участникът е живял при родителите си, след което е напуснал и сключил брак на 21 години и 4 години по-късно е имал дете. Състоянието не се променя след това до 30 годишна възраст.
Вероятности за преминаване от едно състояние в друго
С функцията crosstab() първо ще изчислим за всеки ред броя преходи между отделните състояния, след което ще ги групираме и агрегираме с функцията sum() в една обща кръстосана таблица и накрая ще разделим на общия брой за всеки ред, за да получим условните вероятности.
# Създаване на празен списък
trans_matrix = []
# Изчисляване на броя преходи между отделните състояния
for row in df_seq_values:
trans_matrix.append(pd.crosstab(pd.Series(row[:-1], name='->'),
pd.Series(row[1:], name='to state')))
# Сумиране на стойностите в списъка
trans_matrix = pd.concat(dict(enumerate(trans_matrix))).sum(level=1)
# Сменяне на местата на индексите и колоните
trans_matrix = trans_matrix[['P', 'L', 'M', 'LM', 'C', 'LC', 'LMC', 'D']].reindex(['P', 'L', 'M', 'LM', 'C', 'LC', 'LMC', 'D'])
# Изчисляване на условните вероятности
trans_matrix = trans_matrix.div(trans_matrix.sum(axis=1), axis=0)
Резултат:
-> | P | L | M | LM | C | LC | LMC | D |
---|---|---|---|---|---|---|---|---|
P | 0.886 | 0.055 | 0.015 | 0.032 | 0 | 0.001 | 0.011 | 0 |
L | 0 | 0.89 | 0 | 0.083 | 0 | 0.004 | 0.023 | 0 |
M | 0 | 0 | 0.969 | 0.01 | 0 | 0 | 0.011 | 0.01 |
LM | 0 | 0 | 0 | 0.787 | 0 | 0 | 0.199 | 0.014 |
C | 0 | 0 | 0.125 | 0 | 0.812 | 0.062 | 0 | 0 |
LC | 0 | 0 | 0 | 0 | 0 | 0.882 | 0.118 | 0 |
LMC | 0 | 0 | 0 | 0 | 0 | 0 | 0.994 | 0.006 |
D | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
Получената таблица е по-различна от стандартната кръстосана таблица, която е симетрична (A -> B е равно на B -> A) и съответно стойностите по диагонала са равни на единица. Тук това не е така и елементите по диагонала отразяват вероятността даденият човек да се намира в конкретното състояние. Най-стабилно е LMC, т.е. участниците, които са семейни, не живеят при родителите и имат деца – 0.994. Другото стабилно състояние е M (сключили брак) със стойност 0.969. При D (разведените) се е получила единица, защото това е последното състояние и данните са ограничени, тъй като обхващат 30 годишен период.
Средна продължителност за пребиваване в състояние
С помощта на стълбовидни диаграми ще представим графично колко време средно участниците пребивават в дадено състояние. Първо ще видим общо за всички участници, а след това според колоната пол.
# Откриване на броя събития по редове
seq_counts = df_seq.apply(pd.Series.value_counts, axis=1).fillna(0).astype(int)
# Изчисляване на средните стойности за всяко състояние
seq_count_means = pd.DataFrame(seq_counts.mean(), columns=['mean'])
# Създаване на фигура
fig = go.Figure()
# Изграждане на стълбовидна диаграма
fig.add_trace(go.Bar(name=str(seq_count_means.index),
x=seq_count_means.index,
y=seq_count_means['mean']))
# Настройки на визуализацията
fig.update_layout(title_text='Средна продължителност за пребиваване в състояние',
yaxis = dict(title='Mean time (n=2000)',
tickmode='linear'))
# Показване на визуализацията
fig.show()
Най-високо средно време участниците прекарват в състояние P (при родителите). На второ и трето място по средно време са състоянията L (живеят самостоятелно) и LMC (живеят самостоятелно, имат брак и деца).
Нека сега да видим какви са разликите в средното време за пребиваване в дадено състояние при мъже и жени.
# Добавяне на нова колона за пол
seq_counts['sex'] = df['sex']
# Откриване на средните стойности
seq_counts_gender = seq_counts.groupby('sex').mean().T
# Премахване на колоната за пол
seq_counts.drop('sex', axis=1, inplace=True)
# Създаване на празен списък
data_means = []
# Добавяне на стълбовидни диаграми към празния списък
for x in seq_counts_gender.columns:
data_means.append(go.Bar(name=str(x),
x=seq_counts_gender.index,
y=seq_counts_gender[x]))
# Създаване на фигура с данните
fig = go.Figure(data_means)
# Настройки на визуализацията
fig.update_layout(title_text='Средна продължителност за пребиваване в състояние по пол',
yaxis = dict(title='Mean time (n=2000)',
tickmode='linear'))
# Показване на визуализацията
fig.show()
От визуализацията става ясно, че мъжете средно живеят по-дълго време при родителите, докато жените по-продължително време са семейни с деца.
Визуализация на определени последователности
На следващите визуализации ще представим определени последователности за конкретни диапазони от индекси. Първо ще видим началните 10, а след това за всички 2000 наблюдения.
# Създаване на нов DataFrame с първите 10 визуализации
seq_counts10 = seq_counts.head(10)
# Създаване на масив с имената на колоните от а15 до а30
ages = np.array(df_seq.columns)
# Създаване на празен списък
data = []
# Добавяне на стълбовидни диаграми към празния списък
for x in seq_counts10.columns:
data.append(go.Bar(name=str(x),
x=seq_counts10[x],
y=seq_counts10.index,
orientation='h',
text=seq_counts10[x],
hovertemplate=
'<br><b>Years in state:</b> %{text} '))
# Създаване на фигура с данните
fig = go.Figure(data)
# Настройки на визуализацаията
fig.update_layout(barmode = 'stack',
title_text='Първите 10 последователности от събития',
bargap=0,
xaxis = dict(
tickmode = 'array',
tickvals = np.arange(1,17),
ticktext = ages,
),
yaxis = dict(tickmode='linear'))
# Показване на визуализацията
fig.show()
Нека погледнем например реда с индекс 4. Този участник е живял при родителите си до 19 годишна възраст, след което е напуснал дома и на 27 години е сключил брак и е имал дете.
# Създаване на празен списък
data = []
# Добавяне на стълбовидни диаграми към празния списък
for x in seq_counts.columns:
data.append(go.Bar(name=str(x),
x=seq_counts[x],
y=seq_counts.index,
orientation='h',
text=seq_counts[x],
hovertemplate=
'<br><b>Years in state:</b> %{text} '))
# Създаване на фигура с данните
fig = go.Figure(data)
# Настройки на визуализацията
fig.update_layout(barmode = 'stack',
title_text='Index plot',
height=600,
width=800,
bargap=0,
xaxis = dict(
tickmode = 'array',
tickvals = np.arange(1,17),
ticktext = ages,
),
yaxis=dict(title='2000 seq. (n=2000)'))
fig.update_traces(marker_line_width=0)
# Показване на визуализацията
fig.show()
На тази визуализация можем да видим, че синьото надделява над останалите цветове до около 19 годишна възраст, което отново потвърждава извода, който направихме по-рано, че участниците прекарват най-много време, живеейки при родителите си. След 21 години прави впечатление най-вече розовият цвят, който е за състоянието LMC, т.е. след тази възраст повечето участници живеят самостоятелно, имат сключен брак и дете.
Използване на алгоритъма PrefixSpan за откриване на чести последователности
Алгоритъмът PrefixSpan е начин да открием кои последователности от състояния/събития се срещат по-често в нашата извадка. Това става на базата на предварително зададен праг (threshold) за честота на срещане.
Алгоритъмът първо открива тези, които са с дължина от 1 елемент, след което продължава с последователности от повече елементи. Полученият резултат е в следния вид:
<последователност> : <честота на срещане>
За нашия пример, нека кажем, че искаме да видим само онези последователности, които се срещат в 25% от данните. Тъй като имаме 2000 наблюдения, е нужно да видим тези последователности, които се срещат в минимум 500 от тях.
Ще използваме пакета на Python – prefixspan за тази цел.
# Създаване на масив от данните с последователностите
data_ps = np.array(df_seq)
# Използване на функцията PrefixSpan
ps = PrefixSpan(data_ps)
# Откриване на честите последователности
frequents = ps.frequent(500)
# Създаване на нов DataFrame
frequents_df = pd.DataFrame(frequents, columns=['count', 'frequent sequence pattern'])
count | frequent sequence pattern | |
---|---|---|
0 | 1972 | ['P'] |
1 | 1896 | ['P', 'P'] |
2 | 1847 | ['P', 'P', 'P'] |
3 | 1762 | ['P', 'P', 'P', 'P'] |
4 | 1631 | ['P', 'P', 'P', 'P', 'P'] |
5 | 1411 | ['P', 'P', 'P', 'P', 'P', 'P'] |
6 | 1191 | ['P', 'P', 'P', 'P', 'P', 'P', 'P'] |
7 | 1000 | ['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'] |
8 | 833 | ['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'] |
9 | 682 | ['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'] |
10 | 509 | ['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'] |
11 | 564 | ['P', 'P', 'P', 'P', 'P', 'P', 'P', 'LM'] |
12 | 677 | ['P', 'P', 'P', 'P', 'P', 'P', 'LM'] |
13 | 604 | ['P', 'P', 'P', 'P', 'P', 'P', 'LMC'] |
14 | 543 | ['P', 'P', 'P', 'P', 'P', 'P', 'LMC', 'LMC'] |
15 | 792 | ['P', 'P', 'P', 'P', 'P', 'LM'] |
16 | 538 | ['P', 'P', 'P', 'P', 'P', 'LM', 'LM'] |
17 | 712 | ['P', 'P', 'P', 'P', 'P', 'LMC'] |
18 | 638 | ['P', 'P', 'P', 'P', 'P', 'LMC', 'LMC'] |
19 | 556 | ['P', 'P', 'P', 'P', 'P', 'LMC', 'LMC', 'LMC'] |
От таблицата става ясно, че най-често срещаните последователности са тези, при които участникът живее известно време при родителите, след което сключва брак и напуска дома. В повече от 700 от всички случаи има и деца.
Как са разпределени състоянията във времето и какво се променя?
Със следващите графики ще представим разпределението на състоянията във времето. Първо ще видим общо за всички наблюдения, а след това според пол и роден език.
# Създаване на празен списък
data = []
# Добавяне на стълбовидни диаграми към списъка
for x in frequencies.columns:
data.append(go.Bar(name=str(x),
x=frequencies.index,
y=frequencies[x]))
# Създаване на фигура с данните
fig = go.Figure(data)
# Настройки на визуализацията
fig.update_layout(barmode = 'stack',
title_text='Обща Хронограма',
bargap=0)
# Показване на визуализацията
fig.show()
Можем да кажем, че 50% от участниците на възраст 22 години живеят при родителите си, след това с нарастване на възрастта това започва да се променя. Има много малко хора, които са разведени и те са след 25 годишна възраст. Преди това са много редки такива случаи.
# Създаване на списъци със стойностите за пол и роден език
genders = ['man', 'woman']
languages = ['french', 'german', 'italian']
# Филтриране на данните по определен пол и роден език и запазване в нови променливи
for gender in genders:
locals()['df_seq_'+str(gender)] = df[df['sex']==gender].iloc[:,9:25]
for lang in languages:
locals()['df_seq_'+str(lang)] = df[df['plingu02']==lang].iloc[:,9:25]
# Създаване на списъци с променливите и отделните наименования
dframes = [df_seq_man, df_seq_woman, df_seq_french, df_seq_german, df_seq_italian]
names = ['man','woman','french','german','italian']
# Откриване на честотите за филтрираните данни по определен пол и роден език
for name, frame in zip(names,dframes):
frame.replace({0:'P', 1:'L', 2:'M', 3:'LM', 4:'C', 5:'LC', 6:'LMC', 7:'D'}, inplace=True)
locals()['frequencies_'+str(name)] = frame.apply(pd.Series.value_counts, axis=0).fillna(0) / frame.shape[0]
# Създаване на списък с променливите, съдържащи стойностите за честотите
frequencies_list = [frequencies_man, frequencies_woman, frequencies_french, frequencies_german, frequencies_italian]
# Разместване на индексите и транспониране на данните
for name, frame in zip(names, frequencies_list):
locals()['frequencies_'+str(name)+'_t'] = frame.reindex(['P','L','M','LM','C','LC','LMC','D']).T
# Създаване на списък с новите данни
transposed_freqs = [frequencies_man_t, frequencies_woman_t, frequencies_french_t, frequencies_german_t, frequencies_italian_t]
# Създаване на празен списък с определен брой елементи и списък с наименованията на визуализациите
data = [ [] for _ in range(len(transposed_freqs)) ]
labels = ['мъже', 'жени', 'хора с роден език френски', 'хора с роден език немски', 'хора с роден език италиански']
# Изграждане на визуализациите
for frame, index, label in zip(transposed_freqs, range(5), labels):
# Добавяне на стълбовидни диаграми към празния списък
for x in frame.columns:
data[index].append(go.Bar(name=str(x),
x=frame.index,
y=frame[x]))
# Създаване на фигурата с данните
fig = go.Figure(data[index])
# Настройки на визуализацията
fig.update_layout(barmode = 'stack',
title_text='Хронограма ' + label,
bargap=0)
# Показване на визуализацията
fig.show()
Хронограмите според пола на участниците изглеждат по следния начин:
Има доста повече разведени при жените отколкото при мъжете. На 23 годишна възраст 51% от мъжете живеят при родителите си, за разлика от жените, от които 34% са в това състояние на тази възраст. Можем да кажем, че жените по-рано напускат родния дом и живеят самостоятелно. Също отново можем да достигнем до извода, че повече жени са със сключен брак и имат деца отколкото мъже.
Хронограмите според родния език на участниците са следните:
18% от всички италианци, на 29 годишна възраст все още живеят при родителите си за разлика от французите и германците, които са респективно 7% и 9%. За сметка на това обаче само 1% от италианците са разведени. Най-голям е броят на разведените при французите.
Ентропия
Като метрика за оценка на състоянията ще използваме ентропия. Тя е мярка за неопределеност и се повишава с нарастване на състоянията. Ентропията достига максимум, когато всички състояния са с равна вероятност и може да е вертикална или хоризонтална
При изчисление на ентропията ще използваме основа 8, защото имаме 8 възможни състояния. Необходимо е първо да открием стойностите на метриката по колони (вертикална), след което ще визуализираме данните в линейна диаграма.
# Изчисление на ентропията по колони
entropy_df = pd.DataFrame({'ages': frequencies.T.columns,
'entropy': np.round(entropy(frequencies.T, base=8), 3)})
# Създаване на фигура
fig = go.Figure()
# Изграждане на визуализация
fig.add_trace(go.Scatter(x = entropy_df['ages'],
y= entropy_df['entropy']))
# Настройки на визуализацията
fig['layout']['title'] = 'Entropy index line chart'
fig['layout']['xaxis']['title'] = 'Index'
fig['layout']['yaxis']['title'] = 'Entropy index (n=2000)'
# Показване на визуализацията
fig.show()
От графиката можем да достигнем до следните изводи:
- на 15 години всички се намират в едно и също състояние (при родителите) ~ 0
- между 24-27 години – активна смяна на семейния статус
- над 27 има лек спад, но нивото остава високо
Нека сега да видим какво е положението според пола на участниците и годините на раждане. Ще изчислим ентропията по редове (хоризонтална), след което ще запазим данните в нов DataFrame и след това ще изградим диаграми тип кутия.
# Изчисляване на ентропията по редове
entropy_rows = pd.DataFrame({'entropy': entropy(seq_counts, base=8, axis=1)})
# Разделяне на данните по години
df['yr_cuts'] = pd.cut(df['birthyr'],
bins=[1909, 1918, 1928, 1938, 1948, 1958],
labels=['1909-18', '1919-28', '1929-38', '1939-48', '1949-58'])
# Създаване на нов DataFrame
df_ge_yr = pd.DataFrame({'sex': df['sex'], 'yrs': df['yr_cuts'], 'entropy': entropy_rows['entropy']})
# Сортиране на стойностите по колоната с годините
df_ge_yr.sort_values(by='yrs', inplace=True)
В следната таблица са представени 5 случайни реда от данните, които ще използваме за изграждане на визуализациите по-надолу:
sex | yrs | entropy | |
---|---|---|---|
940 | man | 1929-38 | 0.37388 |
61 | woman | 1939-48 | 0.329566 |
1298 | man | 1949-58 | 0.423927 |
1103 | woman | 1949-58 | 0.473246 |
1392 | woman | 1949-58 | 0.5 |
# Създаване на фигура
fig = go.Figure()
# Изграждане на визуализация
fig.add_trace(go.Box(x=df_ge_yr['sex'],
y=df_ge_yr['entropy'],
boxmean=True))
# Настройки на визуализацията
fig['layout']['title'] = 'Entropy boxplots by gender'
fig['layout']['yaxis']['title'] = 'Entropy index'
# Показване на визуализацията
fig.show()
На визуализацията можете да видите диаграми тип кутия, представящи разпределението на ентропията според пола на участниците. Можем да кажем, че жените са по-склонни да сменят социалния си статус отколкото мъжете.
# Създаване на фигура
fig = go.Figure()
# Изграждане на визуализацията
fig.add_trace(go.Box(x=df_ge_yr['sex'],
y=df_ge_yr['entropy'],
boxmean=True))
# Настройки на визуализацията
fig['layout']['title'] = 'Entropy boxplots by gender'
fig['layout']['yaxis']['title'] = 'Entropy index'
# Показване на визуализацията
fig.show()
Необходимо е към годините на раждане да добавим съответните години от колоните a15-a30, за да добием представа за кой период става въпрос. От визуализацията можем да стигнем до извод, че през 70-те и 80-те години на XX век, хората са били по-активни в смяната на своя социален статус, отколкото в първата половина на века.
Цялостния пример можете да изтеглите от тук.
Извод
С получените резултати от анализа на състояния и събития можем да намерим отговорите на конкретни бизнес въпроси като например кои са типичните последователности, има ли прилики между тях и можем ли да отделим определени модели. Това би помогнало много за подобряването на работата на бизнеса.
Искате да научите повече за машинното обучение?
Включете се в курса по машинно обучение и анализ на данни с Python.
Автор: Десислава Христова