Автор: Десислава Христова
Анализът на последователност от състояния/събития е една доста интересна задача, която намира широко приложение в различни области. Например в търговията при анализ на поведението на клиентите или при изследване на определена бизнес ситуация като продажба или друга дейност, която преминава през различни етапи.
С получените резултати от анализа можем да достигнем до конкретни изводи и да предприемем действия, с които да подпомогнем работата на бизнеса.
Тази тема беше разгледана на нашия семинар от 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.
1 2 |
# Извеждане на описателните статистики за числови променливи 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.
1 2 |
# Извеждане на описателните статистики за категорийни променливи 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.
1 2 |
# Избиране на определени колони от данните 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 |
Тъй като така с числа е малко по-трудно да разберем за кое състояние точно става въпрос и е нужно да правим справка, ще кодираме стойностите, за да е по-лесна интерпретацията на данните.
1 2 |
# Кодиране на състоянията 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() в една обща кръстосана таблица и накрая ще разделим на общия брой за всеки ред, за да получим условните вероятности.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# Създаване на празен списък 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 годишен период.
С помощта на стълбовидни диаграми ще представим графично колко време средно участниците пребивават в дадено състояние. Първо ще видим общо за всички участници, а след това според колоната пол.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# Откриване на броя събития по редове 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 (живеят самостоятелно, имат брак и деца).
Нека сега да видим какви са разликите в средното време за пребиваване в дадено състояние при мъже и жени.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# Добавяне на нова колона за пол 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 наблюдения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# Създаване на нов 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 години е сключил брак и е имал дете.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# Създаване на празен списък 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 е начин да открием кои последователности от състояния/събития се срещат по-често в нашата извадка. Това става на базата на предварително зададен праг (threshold) за честота на срещане.
Алгоритъмът първо открива тези, които са с дължина от 1 елемент, след което продължава с последователности от повече елементи. Полученият резултат е в следния вид:
<последователност> : <честота на срещане>
За нашия пример, нека кажем, че искаме да видим само онези последователности, които се срещат в 25% от данните. Тъй като имаме 2000 наблюдения, е нужно да видим тези последователности, които се срещат в минимум 500 от тях.
Ще използваме пакета на Python - prefixspan за тази цел.
1 2 3 4 5 6 7 8 9 10 11 |
# Създаване на масив от данните с последователностите 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 от всички случаи има и деца.
Със следващите графики ще представим разпределението на състоянията във времето. Първо ще видим общо за всички наблюдения, а след това според пол и роден език.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# Създаване на празен списък 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 годишна възраст. Преди това са много редки такива случаи.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# Създаване на списъци със стойностите за пол и роден език 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 възможни състояния. Необходимо е първо да открием стойностите на метриката по колони (вертикална), след което ще визуализираме данните в линейна диаграма.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# Изчисление на ентропията по колони 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() |
От графиката можем да достигнем до следните изводи:
Нека сега да видим какво е положението според пола на участниците и годините на раждане. Ще изчислим ентропията по редове (хоризонтална), след което ще запазим данните в нов DataFrame и след това ще изградим диаграми тип "кутия".
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Изчисляване на ентропията по редове 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 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Създаване на фигура 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() |
На визуализацията можете да видите диаграми тип "кутия", представящи разпределението на ентропията според пола на участниците. Можем да кажем, че жените са по-склонни да сменят социалния си статус отколкото мъжете.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Създаване на фигура 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.