Skip to content

Commit 823378f

Browse files
committed
Atualizar exemplo MA
1 parent f5009ea commit 823378f

File tree

1 file changed

+233
-0
lines changed

1 file changed

+233
-0
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
---
2+
jupytext:
3+
formats: md:myst
4+
kernelspec:
5+
display_name: Python 3 (ipykernel)
6+
language: python
7+
name: python3
8+
text_representation:
9+
extension: .md
10+
format_name: myst
11+
format_version: 0.13
12+
jupytext_version: 1.16.4
13+
kernelspec:
14+
display_name: Python 3 (ipykernel)
15+
language: python
16+
name: python3
17+
---
18+
19+
# Exemplo: Arrays com Máscara
20+
21+
Vamos usar arrays com máscara para analisar dados do COVID-19 e lidar com valores ausentes.
22+
23+
24+
***
25+
26+
27+
## O que são arrays com máscara?
28+
29+
Considere o seguinte problema. Você tem um conjunto de dados com entradas inválidas. Se você quiser *pular* ou marcar esses valores indesejados sem apagar dados, você pode usar o [numpy.ma](https://numpy.org/devdocs/reference/maskedarray.generic.html#module-numpy.ma).
30+
31+
Da documentação de [Referência](https://numpy.org/devdocs/reference/maskedarray.generic.html#module-numpy.ma):
32+
33+
> A masked array is the combination of a standard [numpy.ndarray](https://numpy.org/devdocs/reference/generated/numpy.ndarray.html#numpy.ndarray) and a **mask**. A mask is either `nomask`, indicating that no value of the associated array is invalid, or an array of booleans that determines for each element of the associated array whether the value is valid or not. When an element of the mask is `False`, the corresponding element of the associated array is valid and is said to be unmasked. When an element of the mask is `True`, the corresponding element of the associated array is said to be masked (invalid).
34+
35+
Podemos pensar numa [array com máscara](https://numpy.org/devdocs/reference/maskedarray.baseclass.html#numpy.ma.MaskedArray) como uma combinação de:
36+
37+
- Dados, numa `numpy.ndarray` com shape e dtype quaisquer;
38+
- Uma máscara booleana com o mesmo shape dos dados;
39+
- Um valor de preenchimento `fill_value`, que vai substituir as entradas inválidas.
40+
41+
## Quando podem ser úteis?
42+
43+
Existem algumas situações em que arrays com máscara podem ser úteis:
44+
45+
- Quando você quer preservar os valores que foram mascarados sem copiar a array;
46+
- Quando você tem que lidar com muitas arrays, cada uma com sua máscara. Se a máscara é parte da array, você pode evitar bugs e o código pode ficar mais compacto;
47+
- Quando você tem flags diferentes para valores inválidos e ausentes, e quer preservar as flags sem substitui-las, excluindo-as dos cálculos;
48+
- Se você não puder evitar ou eliminar valores ausentes, mas não quer lidar com [NaN (Not a Number)](https://numpy.org/devdocs/reference/constants.html#numpy.nan).
49+
50+
O módulo `numpy.ma` também vem com uma implementação específica da maior parte das [NumPy universal functions (ufuncs)](https://numpy.org/devdocs/glossary.html#term-ufunc), o que permite usar operações e funções vetorizadas (rápidas).
51+
52+
53+
## Usando arrays com máscara para ver dados do COVID-19
54+
55+
Do [Kaggle](https://www.kaggle.com/atilamadai/covid19) a gente pode baixar um conjunto de dados com informações sobre o início da pandemia. Vamos olhar para um subconjunto de dados, contidos no arquivo `who_covid_19_sit_rep_time_series.csv`.
56+
57+
```{code-cell} ipython3
58+
import numpy as np
59+
import os
60+
# A função os.getcwd() retorna o diretório atual
61+
filepath = os.getcwd()
62+
filename = os.path.join(filepath, "dados", "who_covid_19_sit_rep_time_series.csv")
63+
```
64+
65+
O arquivo tem dados de diferentes tipos organizado assim:
66+
67+
- A primeira coluna é um cabeçalho, que descreve os dados em cada coluna. A partir da quarta coluna, denota a data da coleta dos dados correspondentes.
68+
- Da segunda até a sétima coluna, temos um resumo dos dados que será excluido da nossa análise.
69+
- Os dados numéricos em que estamos interessados começam na coluna 4, linha 8.
70+
71+
Vamos explorar os dados deste arquivo para os primeiros 14 dias de registros. Para coletarmos os dados do arquivo `.csv`, vamos usar a função [numpy.genfromtxt](https://numpy.org/devdocs/reference/generated/numpy.genfromtxt.html#numpy.genfromtxt).
72+
73+
```{code-cell} ipython3
74+
# Vamos usar skip_header e usecols para ler apenas um pedaço do arquivo.
75+
dates = np.genfromtxt(filename, dtype=np.unicode_, delimiter=",",
76+
max_rows=1, usecols=range(3, 17),
77+
encoding="utf-8-sig")
78+
79+
locations = np.genfromtxt(filename, dtype=np.unicode_, delimiter=",",
80+
skip_header=7, usecols=(0, 1),
81+
encoding="utf-8-sig")
82+
83+
nbcases = np.genfromtxt(filename, dtype=np.int_, delimiter=",",
84+
skip_header=7, usecols=range(3, 17),
85+
encoding="utf-8-sig")
86+
```
87+
88+
Na chamada de `numpy.genfromtxt`, também selecionamos o [numpy.dtype](https://numpy.org/devdocs/reference/generated/numpy.dtype.html#numpy.dtype) para cada subconjunto dos dados (um inteiro - `numpy.int_` - ou texto - `numpy.unicode_`). Também usamos o argumento `encoding` para selecionar `utf-8-sig` como a [codificação do arquivo](https://docs.python.org/3/library/codecs.html#encodings-and-unicode).
89+
90+
91+
## Explorando os dados
92+
93+
Vamos selecionar apenas algumas datas para mostrar no nosso gráfico (os [x-axis ticks](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.xticks.html#matplotlib.pyplot.xticks)). Usamos `nbcases.T` para plotar cada linha do arquivo como uma curva do gráfico.
94+
95+
```{code-cell} ipython3
96+
%matplotlib widget
97+
import matplotlib.pyplot as plt
98+
selected_dates = [0, 3, 11, 13]
99+
plt.plot(dates, nbcases.T, '--');
100+
plt.xticks(selected_dates, dates[selected_dates]);
101+
plt.title("COVID-19 cumulative cases from Jan 21 to Feb 3 2020");
102+
```
103+
104+
O gráfico está esquisito entre 24 de Janeiro e 1 de Fevereiro. Olhando para a coluna `locations`, temos os nomes de regiões e países. Mas só as primeiras linhas tem dados na primeira coluna (nomes de província na China). Vamos então agrupar os dados da China numa única linha, usando [numpy.sum](https://numpy.org/devdocs/reference/generated/numpy.sum.html#numpy.sum) para somar todas a linhas selecionadas (`axis=0`):
105+
106+
```{code-cell} ipython3
107+
china_total = nbcases[locations[:, 1] == 'China'].sum(axis=0)
108+
china_total
109+
```
110+
111+
Mas tem algo errado: não é pra termos dados negativos...
112+
113+
114+
## Dados ausentes
115+
116+
```{code-cell} ipython3
117+
nbcases
118+
```
119+
120+
Os `-1` são causados pela tentativa da `numpy.genfromtxt` de ler dados ausentes do arquivo `.csv` original.
121+
122+
```{code-cell} ipython3
123+
from numpy import ma
124+
nbcases_ma = ma.masked_values(nbcases, -1)
125+
```
126+
127+
Agora:
128+
129+
```{code-cell} ipython3
130+
nbcases_ma
131+
```
132+
133+
Cuidado: o atributo `mask` tem valor `True` em elementos com valores **inválidos**.
134+
135+
136+
Vamos excluir a primeira linha (dados da província de Hubei na China):
137+
138+
```{code-cell} ipython3
139+
plt.plot(dates, nbcases_ma[1:].T, '--');
140+
plt.xticks(selected_dates, dates[selected_dates]);
141+
plt.title("COVID-19 cumulative cases from Jan 21 to Feb 3 2020");
142+
```
143+
144+
```{code-cell} ipython3
145+
china_masked = nbcases_ma[locations[:, 1] == 'China'].sum(axis=0)
146+
china_masked
147+
```
148+
149+
Note que `china_masked` é uma array com máscara e para acessar seus dados precisamos usar o atributo `.data`:
150+
151+
```{code-cell} ipython3
152+
china_total = china_masked.data
153+
china_total
154+
```
155+
156+
Pronto, não temos mais valores negativos. Mas ainda tem algo estranho: mas o número acumulado de casos não poderia diminuir de um dia pro outro (de 835 pra 10, por exemplo). Olhando pros dados, vemos que no período em que tínhamos dados ausentes pra China, tínhamos dados válidos pra Hong Kong, Taiwan, Macau e regiões "Unspecified".
157+
158+
```{code-cell} ipython3
159+
china_mask = ((locations[:, 1] == 'China') &
160+
(locations[:, 0] != 'Hong Kong') &
161+
(locations[:, 0] != 'Taiwan') &
162+
(locations[:, 0] != 'Macau') &
163+
(locations[:, 0] != 'Unspecified*'))
164+
```
165+
166+
Observe que `china_mask` é um array de valores booleanos (`True` ou `False`); podemos verificar que os índices são como queríamos usando o método [ma.nonzero](https://numpy.org/devdocs/reference/generated/numpy.ma.nonzero.html#numpy.ma.nonzero):
167+
168+
```{code-cell} ipython3
169+
china_mask.nonzero()
170+
```
171+
172+
Pronto:
173+
174+
```{code-cell} ipython3
175+
china_total = nbcases_ma[china_mask].sum(axis=0)
176+
china_total
177+
```
178+
179+
Substituindo esses dados, temos:
180+
181+
```{code-cell} ipython3
182+
fig = plt.figure()
183+
plt.plot(dates, china_total.T, '--');
184+
plt.xticks(selected_dates, dates[selected_dates]);
185+
plt.title("COVID-19 cumulative cases from Jan 21 to Feb 3 2020 - Mainland China");
186+
```
187+
188+
## Ajuste de dados
189+
190+
Uma possibilidade seria interpolar os dados ausentes para estimar o número de casos no final de janeiro.
191+
192+
```{code-cell} ipython3
193+
china_total.mask
194+
invalid = china_total[china_total.mask]
195+
invalid
196+
```
197+
198+
Podemos acessar os valores válidos usando o operador de negação:
199+
200+
```{code-cell} ipython3
201+
valid = china_total[~china_total.mask]
202+
valid
203+
```
204+
205+
Agora, para criar uma aproximação simples para estes dados, devemos levar em conta as entradas válidas perto das inválidas. Vamos selecionar as datas em que os dados são válidos; observe que podemos usar a máscara do array `china_total` também no array de datas:
206+
207+
```{code-cell} ipython3
208+
dates[~china_total.mask]
209+
```
210+
211+
Finalmente, vamos usar [numpy.polyfit](https://numpy.org/devdocs/reference/generated/numpy.polyfit.html#numpy.polyfit) e [numpy.polyval](https://numpy.org/devdocs/reference/generated/numpy.polyval.html#numpy.polyval) para criar um polinômio cúbico que faz um ajuste dos dados:
212+
213+
```{code-cell} ipython3
214+
t = np.arange(len(china_total))
215+
params = np.polyfit(t[~china_total.mask], valid, 3)
216+
cubic_fit = np.polyval(params, t)
217+
fig = plt.figure()
218+
plt.plot(t, china_total);
219+
plt.plot(t, cubic_fit, '--');
220+
```
221+
222+
Melhorando o gráfico:
223+
224+
```{code-cell} ipython3
225+
plt.plot(t, china_total);
226+
plt.plot(t[china_total.mask], cubic_fit[china_total.mask], '--', color='orange');
227+
plt.plot(7, np.polyval(params, 7), 'r*');
228+
plt.xticks([0, 7, 13], dates[[0, 7, 13]]);
229+
plt.yticks([0, np.polyval(params, 7), 10000, 17500]);
230+
plt.legend(['Mainland China', 'Cubic estimate', '7 days after start']);
231+
plt.title("COVID-19 cumulative cases from Jan 21 to Feb 3 2020 - Mainland China\n"
232+
"Cubic estimate for 7 days after start");
233+
```

0 commit comments

Comments
 (0)