Sepsis Detection

Objetivo

A sepse é uma síndrome clínica decorrente de uma infecção associada a uma inflamação sistêmica, onde fatores patogênicos e características do hospedeiro (idade, comorbidades, genética, ambiente) determinam a gravidade e a evolução da doença.

No Brasil, segundo o ILAS (Instituto Latino Americano da Sepse), de acordo com o seu último relatório nacional, a mortalidade por sepse é de 40%, incluindo hospitais públicos e privados. Além do impacto na mortalidade, devido à complexidade dos casos, metade dos pacientes precisam ser tratados em Unidades de Terapia Intensiva (UTI) totalizando 25% da ocupação dos leitos de UTI no Brasil, sendo a sepse uma das doenças que gera mais custos no setor público e privado da saúde no país.

O grande problema com o diagnóstico preciso de sepse é que ele envolve a obtenção de amostras de sangue e urina do paciente, e até que sejam aferidas às quantidades relativas de lactato, leucócitos e proteína C reativa ou feita a cultura de urina, que são indicadores mais precisos de sepse, o paciente pode apresentar uma piora exponencial no seu quadro clínico.

Trechos retirados da dissertação de mestrado de Aline Junskowski Kalil. [1]

Kalil, A. J. (2017). Avaliação do impacto na identificação de pacientes com risco de sepse após implantação de um robô cognitivo gerenciador de riso (Robô Laura) (Master’s thesis, Universidade Tecnológica Federal do Paraná).

[1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
np.random.seed(2021)

[2]:
df_test= pd.read_csv("test_data_without_label.csv")
df_train = pd.read_csv("training_data.csv")

Conhecendo os dados

ID N° de Atendimento (N° do Paciente) Temperatura Pulso Respiração Pa_min (Pressão Mínima) Pa_max (Pressão Máxima)

[3]:
df_train.head()
[3]:
id num_atend temperatura pulso respiracao pa_min pa_max sepse
0 1 6066066 36.0 117.0 NaN 113.0 72.0 1
1 2 6019916 36.0 105.0 NaN NaN NaN 1
2 3 6000000 38.0 118.0 NaN 110.0 70.0 1
3 4 5993343 37.0 136.0 NaN NaN NaN 1
4 5 6001799 37.0 104.0 NaN NaN NaN 1
[4]:
id = df_test.id #sera utilizado para prever depois

df_train.drop(['id', 'num_atend'], axis=1, inplace= True)

df_test.drop(['id', 'num_atend'], axis=1, inplace= True)
[5]:
y = df_train.sepse.value_counts()/df_train.sepse.value_counts().sum() #frequencia absoluta
plt.bar(['0','1'],y)
plt.title('Frequencia absoluta de Sepse')
plt.show()
../../_images/Jupyter_Sepsis_Detection_Final_Sepse_9_0.png

Pre processamento

[6]:
nLinhas , nColunas = df_train.shape

dupl = df_train.duplicated().sum()/nLinhas

print('Porcentagem de linhas duplicadas:', round(dupl*100,2))
Porcentagem de linhas duplicadas: 70.31
[7]:
#Verificado a quantidade de NaN por atributo
nan_train= pd.DataFrame()
nan_train['Qtd Nan'] = df_train.isna().sum()
nan_train['Qtd Nan %'] = round(100*df_train.isna().sum()/len(df_train),2)
print('Frequencia treino:')
nan_train.head(6)
Frequencia treino:
[7]:
Qtd Nan Qtd Nan %
temperatura 2145 12.66
pulso 2736 16.14
respiracao 13734 81.03
pa_min 8466 49.95
pa_max 8468 49.96
sepse 0 0.00
[8]:
#Verificado a quantidade de NaN por atributo
nan_test= pd.DataFrame()
nan_test['Qtd Nan'] = df_test.isna().sum()
nan_test['Qtd Nan %'] = round(100*df_test.isna().sum()/len(df_test),2)
print('Frequencia teste:')
nan_test.head(6)
Frequencia teste:
[8]:
Qtd Nan Qtd Nan %
temperatura 903 12.37
pulso 1183 16.21
respiracao 6174 84.58
pa_min 3864 52.93
pa_max 3865 52.95

Pelo fato de haver uma quantidade grande de dados faltantes no conjunto de treino e teste, decidimos por utilizar o KNN Imputer para atribuirmos valores aos dados faltantes. O KNN Imputer, como o nome já informa, preenche os valores ausentes usando a abordagem k-vizinhos mais próximos.

Antes de aplicarmos tal método de preenchimento, iremos verificar e tratar os atributos da base.

Tratando o campo temperatura

[9]:
treino = df_train.copy()
teste = df_test.copy()
[10]:
treino.temperatura.unique()
[10]:
array([  36. ,   38. ,   37. ,   35. ,   32. ,    nan,   39. ,   35.8,
          0. ,   34. ,   36.7,   35.6,  356. ,   40. ,  131. ,   36.8,
         35.1,   36.1,   35.4,   33.6,   37.6,   36.6,   35.5,   36.5,
       3602. ,   37.2,    6. ,  378. ,   36.9,  -35. ,   36.2,   36.3,
         37.7,   33. ,   85. ,  336. ,  368. ,   37.5,   35.3,   35.7,
         35.9,   36.4])
[11]:
teste.temperatura.unique()
[11]:
array([ 36. ,  37. ,   nan,  39. ,  36.6,  38. ,  36.7,  35. ,  36.3,
        37.2,  34. ,  33. ,   0. ,  37.5,  35.8,  37.7,  36.8,  35.3,
        36.1,  35.4,  35.5,  36.5,  37.6,  36.2,  35.1,  35.6,  36.9,
       378. ,  36.4, -35. ,  40. ,  35.7,  35.9,  33.6])

Alguns valores como 3602, 378, por exemplo, entende-se que foi algum erro na hora de passar os dados para a planilha, todavia, iremos atribuir nan a esses vaores.

[12]:
inf = 9e999 #Tranforma em infinito
treino.temperatura.replace({3602:inf-inf, 378:inf-inf, 336:inf-inf, 368:inf-inf, 356:inf-inf}, inplace=True)
teste.temperatura.replace(378, np.nan, inplace=True)
[13]:
treino.temperatura = treino.temperatura.apply(abs)
teste.temperatura = teste.temperatura.apply(abs)

Substituir temperatura nula por nan

[14]:
treino.query('temperatura == 0')
[14]:
temperatura pulso respiracao pa_min pa_max sepse
151 0.0 121.0 NaN 180.0 90.0 1
1155 0.0 77.0 NaN 129.0 85.0 0
2115 0.0 84.0 NaN 109.0 81.0 0
2121 0.0 98.0 NaN 145.0 91.0 0
2328 0.0 94.0 NaN 130.0 81.0 0
... ... ... ... ... ... ...
16268 0.0 87.0 18.0 90.0 57.0 0
16278 0.0 87.0 18.0 90.0 57.0 0
16462 0.0 87.0 18.0 90.0 57.0 0
16513 0.0 87.0 18.0 90.0 57.0 0
16752 0.0 87.0 18.0 90.0 57.0 0

194 rows × 6 columns

[15]:
treino.temperatura.replace(0, np.nan, inplace=True)
teste.temperatura.replace(0, np.nan, inplace=True)

Substituir temperatura menor que 20 e maior ou igual a 45 por NaN

[16]:
treino.query('temperatura < 20 or temperatura >= 45')
[16]:
temperatura pulso respiracao pa_min pa_max sepse
577 131.0 106.0 NaN NaN NaN 1
1843 6.0 101.0 NaN 158.0 89.0 0
4451 6.0 87.0 NaN 131.0 80.0 0
7123 85.0 85.0 NaN NaN NaN 0
11772 85.0 85.0 NaN NaN NaN 0
[17]:
treino['temperatura'].values[treino['temperatura'].values < 20] = inf-inf
treino['temperatura'].values[treino['temperatura'].values >= 45] = inf-inf

teste['temperatura'].values[teste['temperatura'].values < 20] = inf-inf
teste['temperatura'].values[teste['temperatura'].values >= 45] = inf-inf
[18]:
treino.temperatura.unique()
[18]:
array([36. , 38. , 37. , 35. , 32. ,  nan, 39. , 35.8, 34. , 36.7, 35.6,
       40. , 36.8, 35.1, 36.1, 35.4, 33.6, 37.6, 36.6, 35.5, 36.5, 37.2,
       36.9, 36.2, 36.3, 37.7, 33. , 37.5, 35.3, 35.7, 35.9, 36.4])
[19]:
teste.temperatura.unique()
[19]:
array([36. , 37. ,  nan, 39. , 36.6, 38. , 36.7, 35. , 36.3, 37.2, 34. ,
       33. , 37.5, 35.8, 37.7, 36.8, 35.3, 36.1, 35.4, 35.5, 36.5, 37.6,
       36.2, 35.1, 35.6, 36.9, 36.4, 40. , 35.7, 35.9, 33.6])
[20]:
treino.temperatura.describe()
[20]:
count    14596.000000
mean        36.187188
std          0.922318
min         32.000000
25%         36.000000
50%         36.000000
75%         36.700000
max         40.000000
Name: temperatura, dtype: float64

Tratando o campo pulso

[21]:
treino.pulso.unique() #batimento cardíaco
[21]:
array([ 117.,  105.,  118.,  136.,  104.,   83.,   91.,   99.,   67.,
        116.,  107.,   72.,   85.,   68.,  102.,  110.,   66.,  139.,
         74.,  101.,  120.,   71.,  144.,   nan,   96.,  108.,   92.,
         98.,   94.,  100.,  114.,   78.,   87.,   97.,  106.,  111.,
        115.,  112.,   64.,   90.,  103.,   76.,  124.,   80.,  119.,
        140.,   75.,  137.,   82.,  125.,  134.,  109.,  122.,   79.,
         95.,  121.,  130.,   65.,   69.,  113.,   73.,   77.,  131.,
         88.,   81.,   93.,  126.,   84.,   56.,  128.,  170.,   60.,
         38.,  154.,  123.,  138.,  155.,   86.,  127.,   70.,   52.,
         89.,  132.,  152.,   62.,  135.,   61.,  149.,  179.,  150.,
        183.,   51.,  165.,   63.,  143.,   44.,  129.,  151.,  156.,
        158.,   55.,   59.,  153.,  174.,  186.,   42.,   40.,  145.,
         32.,  133.,   43.,  142.,   54.,  160.,   53.,   58.,   57.,
       1000.,   50.,   46.,  168.,   30.,   47.,   49.,  175.,   11.,
         10.,  166.,  147.,    0.,   41.,  163.,   48.,  177.,  192.,
        157.,  841.,  148.,  180.,  200.,   36.,  159.,  162.])
[22]:
teste.pulso.unique()
[22]:
array([  93.,  107.,   90.,   69.,   71.,   nan,  104.,  103.,   99.,
         75.,   82.,   73.,   84.,   51.,   98.,   77.,   76.,   66.,
         80.,  110.,   97.,   64.,  100.,  138.,   81.,   91.,   74.,
         89.,   96.,   87.,  135.,   56.,   95.,  102.,   88.,  111.,
        101.,   86.,   68.,   78.,   83.,   94.,  128.,   85.,   62.,
        122.,  119.,  126.,   42.,   72.,   79.,   61.,   92.,   70.,
        112.,  118.,  120.,   57.,   54.,  140.,  105.,  113.,  114.,
        106.,  131.,   67.,   55.,  115.,   63.,   65.,  125.,  121.,
        155.,  144.,  130.,  123.,  109.,   10.,  142.,  116.,  169.,
        129.,  136.,  108.,  117.,   60.,  127.,  133.,   44.,   59.,
        137.,   52.,  124.,  180.,  139.,   35.,   50.,   58.,   38.,
        177.,  156.,  145.,   47.,  168.,  132., 1116.,  163.,   43.,
         53.,   46.,   49.,  178.,  165.,  134.,  149.,  704.,    0.,
         11.,  150.,  170.,  148.,  151.,  166.,  147.,  143.,  160.,
        159.,  158.,  174.,  153.,  157.,  162.,  179.,  183.,  152.,
         23.])
[23]:
treino.query('pulso >= 200') #vamos considerar maior ou igual a 200 nan
[23]:
temperatura pulso respiracao pa_min pa_max sepse
1616 36.0 1000.0 NaN 120.0 66.0 0
6326 36.0 841.0 NaN 128.0 74.0 0
8027 36.0 200.0 NaN NaN NaN 0
14473 36.0 841.0 NaN 128.0 74.0 0
[24]:
treino['pulso'].values[treino['pulso'].values >= 200] = inf-inf
teste['pulso'].values[teste['pulso'].values >= 200] = inf-inf
[25]:
treino.query('pulso == 0') #vamos considerar valor médio arredondado
[25]:
temperatura pulso respiracao pa_min pa_max sepse
4172 36.0 0.0 NaN NaN NaN 0
5523 NaN 0.0 NaN NaN NaN 0
5912 35.0 0.0 NaN NaN NaN 0
6087 35.0 0.0 NaN NaN NaN 0
6952 37.0 0.0 NaN NaN NaN 0
7058 34.0 0.0 NaN NaN NaN 0
7910 37.0 0.0 NaN NaN NaN 0
10652 38.0 0.0 NaN NaN NaN 0
11120 36.0 0.0 NaN NaN NaN 0
11156 NaN 0.0 NaN 90.0 48.0 0
11942 34.0 0.0 NaN NaN NaN 0
13009 NaN 0.0 NaN NaN NaN 0
15104 NaN 0.0 NaN NaN NaN 0
15441 37.0 0.0 NaN NaN NaN 0
[26]:
treino.pulso.replace(0, np.nan, inplace=True)
teste.pulso.replace(0, np.nan, inplace=True)
[27]:
treino.pulso.unique()
[27]:
array([117., 105., 118., 136., 104.,  83.,  91.,  99.,  67., 116., 107.,
        72.,  85.,  68., 102., 110.,  66., 139.,  74., 101., 120.,  71.,
       144.,  nan,  96., 108.,  92.,  98.,  94., 100., 114.,  78.,  87.,
        97., 106., 111., 115., 112.,  64.,  90., 103.,  76., 124.,  80.,
       119., 140.,  75., 137.,  82., 125., 134., 109., 122.,  79.,  95.,
       121., 130.,  65.,  69., 113.,  73.,  77., 131.,  88.,  81.,  93.,
       126.,  84.,  56., 128., 170.,  60.,  38., 154., 123., 138., 155.,
        86., 127.,  70.,  52.,  89., 132., 152.,  62., 135.,  61., 149.,
       179., 150., 183.,  51., 165.,  63., 143.,  44., 129., 151., 156.,
       158.,  55.,  59., 153., 174., 186.,  42.,  40., 145.,  32., 133.,
        43., 142.,  54., 160.,  53.,  58.,  57.,  50.,  46., 168.,  30.,
        47.,  49., 175.,  11.,  10., 166., 147.,  41., 163.,  48., 177.,
       192., 157., 148., 180.,  36., 159., 162.])
[28]:
treino.query('pulso >= 110 and sepse == 1')
[28]:
temperatura pulso respiracao pa_min pa_max sepse
0 36.0 117.0 NaN 113.0 72.0 1
2 38.0 118.0 NaN 110.0 70.0 1
3 37.0 136.0 NaN NaN NaN 1
9 37.0 116.0 NaN 111.0 70.0 1
15 37.0 110.0 NaN NaN NaN 1
... ... ... ... ... ... ...
10317 36.0 116.0 NaN 123.0 77.0 1
10322 NaN 131.0 39.0 82.0 37.0 1
10324 36.8 121.0 12.0 138.0 89.0 1
10325 36.8 110.0 19.0 141.0 61.0 1
10336 36.6 138.0 20.0 119.0 75.0 1

742 rows × 6 columns

Tratando o campo respiração

frequenciarespiratoria.PNG
[29]:
treino.respiracao.unique()
[29]:
array([nan, 15., 33., 17., 22., 19., 16., 12., 23., 26., 27., 28., 10.,
       39., 20., 21., 24., 14., 11., 40., 25., 18., 13.,  0., 29.])
[30]:
treino.query('respiracao == 0') #zero igual a nan
[30]:
temperatura pulso respiracao pa_min pa_max sepse
5308 36.7 103.0 0.0 121.0 70.0 0
13783 36.7 103.0 0.0 121.0 70.0 0
[31]:
treino.respiracao.replace(0, np.nan, inplace=True)
teste.respiracao.replace(0, np.nan, inplace=True)

Tratando o campo pressão minima e máxima

image.png

Através dos valores, entendemos que pressão mínima da base é a sistólica e pressão máxima, Diastólica.

[32]:
treino.pa_min.unique()
[32]:
array([ 113.,   nan,  110.,  153.,  111.,  126.,  107.,  185.,  127.,
        133.,   97.,  191.,  130.,  170.,  143.,  147.,  120.,   90.,
        122.,  138.,  152.,  137.,  142.,  119.,  125.,  115.,  204.,
        150.,  128.,  117.,  118.,  160.,  100.,  155.,  149.,  114.,
        159.,  123.,  108.,  102.,  145.,  180.,  136.,  140.,  169.,
        178.,  144.,  103.,  105.,  109.,   88.,  134.,  101.,  164.,
        167.,   89.,  135.,  132.,  148.,  139.,  131.,   94.,  200.,
        182.,   86.,   81.,  146.,  186.,  121.,  106.,   93.,  112.,
        189.,  154.,  187.,  163.,  129.,  124.,  141.,  116.,   10.,
         98.,  156.,  166.,  151.,  175.,   92.,   79.,  104.,  212.,
         85.,  220.,   91.,  209.,   95.,  165.,  157.,  161.,  171.,
        158.,   82.,   74.,  168.,  173.,   96.,  179.,  162.,  172.,
         99.,   75.,  219.,  184.,  190.,  197.,  230.,  177.,  202.,
        174.,   15.,  224.,  208.,  236.,   40.,  198.,  199.,  203.,
         12.,  215.,  201.,   11.,  217.,  192.,  181.,  210.,  195.,
        183.,  188.,   19.,  176.,  225.,  233.,  193.,   16.,  231.,
         80.,   14.,  213.,  211.,   18.,  194.,   69.,  222.,   83.,
         73.,   67.,  242.,  245.,    0.,   17., 1460.,   20.])
[33]:
treino.query('pa_min == 0') #zero igual a nan
[33]:
temperatura pulso respiracao pa_min pa_max sepse
11982 NaN 63.0 15.0 0.0 0.0 0
13753 NaN 63.0 15.0 0.0 0.0 0
[34]:
treino.pa_min.replace(0, np.nan, inplace=True)
teste.pa_min.replace(0, np.nan, inplace=True)
[35]:
treino.query('pa_min > 220') #recebe nan
[35]:
temperatura pulso respiracao pa_min pa_max sepse
1566 36.0 100.0 NaN 230.0 120.0 0
1765 35.0 85.0 NaN 224.0 101.0 0
2452 NaN 175.0 NaN 236.0 116.0 0
3083 36.0 75.0 NaN 230.0 100.0 0
3153 36.0 84.0 NaN 230.0 112.0 0
4751 35.0 68.0 NaN 225.0 97.0 0
4987 36.0 83.0 NaN 233.0 105.0 0
5273 36.0 80.0 NaN 231.0 140.0 0
7658 35.0 90.0 NaN 222.0 118.0 0
8749 NaN 85.0 NaN 242.0 122.0 0
10457 35.0 88.0 NaN 245.0 100.0 0
11334 36.0 83.0 NaN 233.0 105.0 0
11792 35.0 88.0 NaN 245.0 100.0 0
14329 36.0 100.0 NaN 230.0 120.0 0
14534 36.0 90.0 NaN 1460.0 100.0 0
15247 35.0 88.0 NaN 245.0 100.0 0
16735 36.0 84.0 NaN 230.0 112.0 0
16838 35.0 88.0 NaN 245.0 100.0 0
[36]:
treino['pa_min'].values[treino['pa_min'].values > 220] = inf-inf
teste['pa_min'].values[teste['pa_min'].values > 220] = inf-inf
[37]:
treino.query('pa_min < 80')
[37]:
temperatura pulso respiracao pa_min pa_max sepse
442 37.0 125.0 NaN 10.0 75.0 1
519 36.0 106.0 NaN 79.0 52.0 1
884 37.0 117.0 NaN 74.0 56.0 1
1118 NaN 51.0 NaN 75.0 52.0 0
1706 36.0 71.0 NaN 15.0 89.0 0
2474 35.0 74.0 NaN 40.0 69.0 0
2739 36.0 95.0 NaN 12.0 69.0 0
3108 38.0 85.0 NaN 11.0 75.0 0
3270 35.0 84.0 NaN 11.0 79.0 0
3292 35.0 92.0 NaN 15.0 84.0 0
4278 37.0 117.0 NaN 74.0 56.0 0
4496 36.0 84.0 NaN 19.0 82.0 0
4863 36.0 100.0 NaN 12.0 80.0 0
4961 36.0 67.0 NaN 10.0 71.0 0
5106 36.0 62.0 NaN 16.0 90.0 0
5522 36.0 81.0 NaN 14.0 10.0 0
5636 35.0 90.0 NaN 11.0 75.0 0
5774 36.0 94.0 NaN 10.0 60.0 0
6360 36.0 100.0 NaN 18.0 93.0 0
7089 35.0 89.0 NaN 69.0 60.0 0
7401 36.0 62.0 NaN 18.0 74.0 0
7777 33.0 53.0 NaN 73.0 43.0 0
7843 35.0 180.0 NaN 67.0 43.0 0
8424 36.0 100.0 NaN 16.0 10.0 0
9777 37.0 125.0 NaN 10.0 75.0 1
10069 36.0 106.0 NaN 79.0 52.0 1
12320 38.0 85.0 NaN 11.0 75.0 0
13078 36.0 67.0 NaN 10.0 71.0 0
13548 33.0 53.0 NaN 73.0 43.0 0
13834 36.0 85.0 NaN 17.0 92.0 0
14568 36.0 62.0 NaN 18.0 74.0 0
15837 36.0 71.0 NaN 15.0 89.0 0
15936 36.0 68.0 NaN 20.0 10.0 0
[38]:
treino.pa_max.unique()
[38]:
array([ 72.,  nan,  70., 118.,  56.,  76.,  64.,  75.,  51.,  92., 107.,
        87., 112.,  99., 114.,  59.,  46.,  67.,  83.,  82.,  84.,  96.,
        78.,  69.,  66., 123.,  68.,  79.,  60., 102.,  80.,  54.,  90.,
        73.,  74.,  65.,  77.,  91., 111.,  62.,  93.,  71., 110.,  63.,
        53.,  58.,  61.,  81.,  85.,  48.,  89.,  41.,  57., 100.,  55.,
        86.,  50.,  88.,  47.,  52.,  97., 140., 101., 126.,  94., 108.,
       109., 124.,  95., 106., 113., 115.,  37.,  45., 116.,  98.,  11.,
       103., 117., 120., 119., 104.,  20.,  49., 130., 131., 105.,  10.,
         8., 121., 133., 150., 135.,  43.,   0., 969., 122.,  44., 883.,
       704.])
[39]:
treino.query('pa_max == 0') #zero igual a nan
[39]:
temperatura pulso respiracao pa_min pa_max sepse
7847 36.0 94.0 NaN 121.0 0.0 0
10827 36.0 94.0 NaN 121.0 0.0 0
11982 NaN 63.0 15.0 NaN 0.0 0
13753 NaN 63.0 15.0 NaN 0.0 0
[40]:
treino.pa_max.replace(0, np.nan, inplace=True)
teste.pa_max.replace(0, np.nan, inplace=True)
[41]:
treino.query('pa_max > 180') #recebe nan
[41]:
temperatura pulso respiracao pa_min pa_max sepse
8605 36.0 100.0 NaN 145.0 969.0 0
14740 NaN 92.0 NaN 153.0 883.0 0
15696 NaN 126.0 NaN 197.0 704.0 0
[42]:
treino['pa_max'].values[treino['pa_max'].values > 180] = inf-inf
teste['pa_max'].values[teste['pa_max'].values > 180] = inf-inf

Também substituímos as variáveis contínuas da pressão por dados categóricos, distribuídos nas faixas da imagem que trouxemos logo no início, porém os resultados foram piores.

Medidas para os casos de sepse positiva

[43]:
sepse1 = treino.query('sepse == 1')
[44]:
sepse1.describe()
[44]:
temperatura pulso respiracao pa_min pa_max sepse
count 2336.000000 2406.000000 1046.000000 1692.000000 1691.000000 2575.0
mean 36.335916 99.599751 20.817400 125.989953 67.604376 1.0
std 1.136880 20.942447 5.250119 23.701485 16.540542 0.0
min 32.000000 32.000000 10.000000 10.000000 11.000000 1.0
25% 35.100000 84.000000 18.000000 111.000000 53.000000 1.0
50% 36.100000 101.000000 19.000000 124.000000 67.000000 1.0
75% 37.000000 112.000000 22.000000 143.000000 79.000000 1.0
max 40.000000 186.000000 40.000000 220.000000 140.000000 1.0

Utilizando o KNN imputer para o preenchimento dos dados nan

[45]:
#Divide the features into Independent and Dependent Variable
X = treino.drop('sepse' , axis =1)
y = treino['sepse']
y_completo = y.copy()
[46]:
# knn imputation treino
from numpy import isnan
from sklearn.impute import KNNImputer

# define imputer
imputer = KNNImputer()
# fit on the dataset
imputer.fit(X)
# transform the dataset
Xtrans = imputer.transform(X)
[47]:
Xtrans
[47]:
array([[ 36. , 117. ,  18. , 113. ,  72. ],
       [ 36. , 105. ,  19. , 134.8,  85.4],
       [ 38. , 118. ,  18. , 110. ,  70. ],
       ...,
       [ 35.4,  69. ,  23.4, 149. ,  93. ],
       [ 35.2,  95. ,  19. , 136. ,  82. ],
       [ 37. ,  88. ,  20. , 141.8,  63.4]])
[48]:
#vontando treino para o tipo dataframe
basey = pd.DataFrame()
basey['sepse'] = y
basex = pd.DataFrame(Xtrans, columns=X.columns)
[49]:
#Juntando Treino pós imputação
treino_full = pd.concat([basex, basey], axis=1)
treino_completo = treino_full
[50]:
#Verificado a quantidade de NaN por atributo
nan_train= pd.DataFrame()
nan_train['Qtd Nan'] = treino_full.isna().sum()
nan_train['Qtd Nan %'] = round(100*treino_full.isna().sum()/len(treino_full),2)
print('Frequencia treino:')
nan_train.head(6)
Frequencia treino:
[50]:
Qtd Nan Qtd Nan %
temperatura 0 0.0
pulso 0 0.0
respiracao 0 0.0
pa_min 0 0.0
pa_max 0 0.0
sepse 0 0.0
[51]:
for i in treino_full.drop('sepse', axis = 1).columns:

  sns.set(style="ticks")

  x = treino[i]
  coluna = i
  mu = round(x.mean(),2) # mean of distribution
  sigma = round(x.std(),2)  # standard deviation of distribution

  f, (ax_box, ax_hist) = plt.subplots(2)

  sns.boxplot(x=x, ax=ax_box)
  sns.histplot(x=x, ax=ax_hist)

  ax_box.set(yticks=[])
  sns.despine(ax=ax_hist)
  sns.despine(ax=ax_box, left=True)
  ax_box.set_title('Boxplot e Histograma de {}\n $\mu={}$, $\sigma={}$'.format(coluna, mu,sigma))

plt.show()
../../_images/Jupyter_Sepsis_Detection_Final_Sepse_71_0.png
../../_images/Jupyter_Sepsis_Detection_Final_Sepse_71_1.png
../../_images/Jupyter_Sepsis_Detection_Final_Sepse_71_2.png
../../_images/Jupyter_Sepsis_Detection_Final_Sepse_71_3.png
../../_images/Jupyter_Sepsis_Detection_Final_Sepse_71_4.png

Pelo fato de termos obervados valores positivos e negativos para a sepse em relação às variáveis pulso, pa_min e pa_max nos valores apresentados como outlier, mas olhando a base antes do preenchiemnto dos nan, decidimos por não aumentar mais os valores a serem considerados como nan. Todavia, fica um ponto de atenção a ser tratado logo na coleta dos dados, pois nesses casos, não da para saber se de fato ocorreu o valor apresentado ou se também foi algum tipo de erro.

[52]:
treino.query('pulso >= 130 and sepse == 0').head(10) #185 linhas com sepse == 1 e 209 com sepse == 0
[52]:
temperatura pulso respiracao pa_min pa_max sepse
1287 36.0 135.0 NaN NaN NaN 0
1292 38.0 130.0 NaN 126.0 98.0 0
1303 36.0 144.0 NaN NaN NaN 0
1320 NaN 131.0 NaN NaN NaN 0
1358 35.0 133.0 NaN NaN NaN 0
1480 39.0 140.0 NaN NaN NaN 0
1500 35.0 154.0 NaN 200.0 100.0 0
1644 36.0 130.0 NaN 123.0 77.0 0
1679 36.0 138.0 NaN NaN NaN 0
1695 36.0 152.0 NaN 202.0 119.0 0
[53]:
treino.query('pa_max >= 110 and sepse == 1').head(10) #24 com sepse == 1 e 108 sepse == 0
[53]:
temperatura pulso respiracao pa_min pa_max sepse
8 35.0 67.0 NaN 153.0 118.0 1
14 NaN 102.0 NaN 185.0 118.0 1
28 36.0 104.0 NaN 170.0 112.0 1
32 35.0 108.0 NaN 147.0 114.0 1
76 35.0 90.0 NaN 204.0 123.0 1
149 35.0 74.0 NaN 153.0 114.0 1
163 39.0 75.0 NaN 178.0 111.0 1
204 35.0 84.0 NaN 170.0 110.0 1
276 NaN 107.0 NaN 182.0 114.0 1
301 37.0 115.0 NaN 186.0 112.0 1
[54]:
treino.query('pulso <= 40 and sepse == 1').head(10) #10 sepse == 0 e 4 com sepse == 1
[54]:
temperatura pulso respiracao pa_min pa_max sepse
258 35.0 38.0 NaN 155.0 78.0 1
941 35.0 40.0 NaN NaN NaN 1
965 35.0 32.0 NaN NaN NaN 1
8931 35.0 38.0 NaN 155.0 78.0 1
[55]:
treino.query('pa_max < 40 and sepse == 1') #16 com sepse == 1 e 14 com sepse == 0
[55]:
temperatura pulso respiracao pa_min pa_max sepse
837 NaN 131.0 39.0 82.0 37.0 1
1054 35.0 56.0 NaN 140.0 11.0 1
9004 NaN 131.0 39.0 82.0 37.0 1
9162 NaN 131.0 39.0 82.0 37.0 1
9205 NaN 131.0 39.0 82.0 37.0 1
9478 NaN 131.0 39.0 82.0 37.0 1
9520 NaN 131.0 39.0 82.0 37.0 1
9547 NaN 131.0 39.0 82.0 37.0 1
9772 NaN 131.0 39.0 82.0 37.0 1
9790 NaN 131.0 39.0 82.0 37.0 1
9947 NaN 131.0 39.0 82.0 37.0 1
9979 NaN 131.0 39.0 82.0 37.0 1
10027 NaN 131.0 39.0 82.0 37.0 1
10064 NaN 131.0 39.0 82.0 37.0 1
10098 NaN 131.0 39.0 82.0 37.0 1
10322 NaN 131.0 39.0 82.0 37.0 1

Treino e teste

[56]:
X = treino_full.drop('sepse' , axis =1)
y = treino_full['sepse']
X = X.to_numpy()
y = y.to_numpy()
[57]:
# Importa bibliotecas
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25, stratify = y, random_state=42)
[58]:
from sklearn.preprocessing import MinMaxScaler

MinMax = MinMaxScaler()

X_train = MinMax .fit_transform(X_train)
X_test = MinMax .transform(X_test)

Regressão Logística

[59]:
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import balanced_accuracy_score
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score
from sklearn.metrics import precision_score

precisao = []
f1 = []
acuracia = []

cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=1)

for train_index, test_index in cv.split(X, y):
  x_train, x_test = X[train_index], X[test_index]
  y_train, y_test = y[train_index], y[test_index]
  model = LogisticRegression()
  model.fit(x_train,y_train)
  y_pred = model.predict(x_test)

  precisao.append(precision_score(y_test, y_pred, average="macro"))
  f1.append(f1_score(y_test, y_pred, average="macro"))
  acuracia.append(balanced_accuracy_score(y_test, y_pred))
print('Acuracia média: ', np.mean(acuracia))
print('Precisão média: ', np.mean(precisao))
print('F1 médio: ', np.mean(f1))
Acuracia média:  0.5253959907920419
Precisão média:  0.6583524278348761
F1 médio:  0.5141035935161595

Naive Bayes

[60]:
from sklearn.naive_bayes import GaussianNB

precisao = []
f1 = []
acuracia = []

cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=1)

for train_index, test_index in cv.split(X, y):
  x_train, x_test = X[train_index], X[test_index]
  y_train, y_test = y[train_index], y[test_index]
  model = GaussianNB()
  model.fit(x_train,y_train)
  y_pred = model.predict(x_test)

  precisao.append(precision_score(y_test, y_pred, average="macro"))
  f1.append(f1_score(y_test, y_pred, average="macro"))
  acuracia.append(balanced_accuracy_score(y_test, y_pred))
print('Acuracia média: ', np.mean(acuracia))
print('Precisão média: ', np.mean(precisao))
print('F1 médio: ', np.mean(f1))
Acuracia média:  0.5833666999524743
Precisão média:  0.6585220325685529
F1 médio:  0.5997136272100843

Random forest

[61]:
from sklearn.ensemble import RandomForestClassifier

precisao = []
f1 = []
acuracia = []

cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=1)

for train_index, test_index in cv.split(X, y):
  x_train, x_test = X[train_index], X[test_index]
  y_train, y_test = y[train_index], y[test_index]
  model = RandomForestClassifier()
  model.fit(x_train,y_train)
  y_pred = model.predict(x_test)

  precisao.append(precision_score(y_test, y_pred, average="macro"))
  f1.append(f1_score(y_test, y_pred, average="macro"))
  acuracia.append(balanced_accuracy_score(y_test, y_pred))
print('Acuracia média: ', np.mean(acuracia))
print('Precisão média: ', np.mean(precisao))
print('F1 médio: ', np.mean(f1))
Acuracia média:  0.6619836318079815
Precisão média:  0.7124411235618712
F1 médio:  0.6808835483339327

KNN

[62]:
from sklearn.neighbors import KNeighborsClassifier

precisao = []
f1 = []
acuracia = []

cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=1)

for train_index, test_index in cv.split(X, y):
  x_train, x_test = X[train_index], X[test_index]
  y_train, y_test = y[train_index], y[test_index]
  model = KNeighborsClassifier(n_neighbors= 20)
  model.fit(x_train,y_train)
  y_pred = model.predict(x_test)

  precisao.append(precision_score(y_test, y_pred, average="macro"))
  f1.append(f1_score(y_test, y_pred, average="macro"))
  acuracia.append(balanced_accuracy_score(y_test, y_pred))
print('Acuracia média: ', np.mean(acuracia))
print('Precisão média: ', np.mean(precisao))
print('F1 médio: ', np.mean(f1))
Acuracia média:  0.6175641714353243
Precisão média:  0.7199765038567818
F1 médio:  0.6417681902289986

Preenchendo os NAs do teste com o KNNImputer

[63]:
# knn imputation teste
from numpy import isnan
from sklearn.impute import KNNImputer

imputer = KNNImputer()
# fit on the dataset
imputer.fit(teste)
# transform the dataset
Xtransteste = imputer.transform(teste)

#Teste pós imputação
teste = pd.DataFrame(Xtransteste, columns = teste.columns)

Submetendo os resultados no Kaggle usando o modelo de classificação: Floresta Aleatória

[64]:
RF_model = RandomForestClassifier()
X = treino_completo.drop('sepse', axis =1)
y = y_completo

RF_model.fit(X,y)

y_pred = RF_model.predict(teste)
y_pred = np.array(y_pred, dtype = int)
prediction = pd.DataFrame()
prediction['id'] = id
prediction['sepse'] = y_pred

prediction['sepse'].value_counts()
[64]:
0    6718
1     582
Name: sepse, dtype: int64
[65]:
prediction.to_csv('RFsimples.csv', index = False)

0.91411 Kaggle foi nossa melhor pontuação

[66]:
prediction.head(10)
[66]:
id sepse
0 1 0
1 2 0
2 3 0
3 4 0
4 5 0
5 6 0
6 7 0
7 8 0
8 9 0
9 10 0

Submetendo os resultados no Kaggle usando o modelo de classificação: KNN

[67]:
KNN_model = KNeighborsClassifier(n_neighbors= 20)
X = treino_completo.drop('sepse', axis =1)
y = y_completo

KNN_model.fit(X,y)

y_pred = KNN_model.predict(teste)
y_pred = np.array(y_pred, dtype = int)
prediction = pd.DataFrame()
prediction['id'] = id
prediction['sepse'] = y_pred

prediction['sepse'].value_counts()
[67]:
0    6715
1     585
Name: sepse, dtype: int64
[68]:
prediction.to_csv('KNN20.csv', index = False)