-
Notifications
You must be signed in to change notification settings - Fork 0
/
05-programacao.Rmd
641 lines (518 loc) · 18.9 KB
/
05-programacao.Rmd
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
# Programando com dados
Por quê programar?
- Evitar repetições desnecessárias de análises ou cálculos que são
repetidos com frequência.
- Documentar as etapas que você realizou para chegar a um
resultado.
- Fácil recuperação e modificação de programas.
Como programar?
- Criando programas! (Scripts, rotinas, **algoritmos**).
- Crie uma sequência lógica de comandos que devem ser executados
em ordem.
- Utilize as ferramentas básicas da programação: **estruturas de
repetição** (`for()`) e **estruturas de seleção** (`if()`).
## Estrutura de repetição `for()`
Serve para repetir um ou mais comandos diversas vezes. Para ver como
funciona, considere o seguinte exemplo:
```{r}
for(i in 1:10){
print(i)
}
```
O resultado é a chamada do comando `print()` para cada valor que o
índice `i` recebe (nesse caso `i` recebe os valores de 1 a 10).
A sintaxe será sempre nesse formato:
```{r, eval=FALSE}
for(<índice> in <valores>){
<comandos>
}
```
Veja outro exemplo em como podemos aplicar o índice:
```{r}
x <- 100:200
for(j in 1:10){
print(x[j])
}
```
Veja que o índice não precisa ser `i`, na verdade pode ser qualquer
letra ou palavra. Nesse caso, utilizamos os valores como índice
para selecionar elementos de `x` nas posições específicadas.
Um outro exemplo seria se quisessemos imprimir o quadrado de alguns
números (não necessariamente em sequência):
```{r}
for(i in c(2, 9, 4, 6)){
print(i^2)
}
```
Ou mesmo imprimir caracteres a partir de um vetor de caracteres:
```{r}
for(veiculos in c("carro", "ônibus", "trem", "bicicleta")){
print(veiculos)
}
```
**Exemplo**: cálculo de notas de uma disciplina.
```{r, echo = FALSE}
## Importa os dados
notas <- read.table("notas.csv", header = TRUE, sep = ";", dec = ",")
```
```{r, eval = FALSE}
## Importa os dados
url <- "http://leg.ufpr.br/~fernandomayer/data/notas.csv"
notas <- read.table(url, header = TRUE, sep = ";", dec = ",")
## Analisa a estrutura dos dados
str(notas)
head(notas)
summary(notas)
```
Antes de seguir adiante, veja o resultado de
```{r eval=FALSE}
for(i in 1:30){
print(notas[i, c("prova1", "prova2", "prova3")])
}
```
Para calcular as médias das 3 provas, precisamos inicialmente de um
vetor para armazenar os resultados. Esse vetor pode ser um novo objeto
ou uma nova coluna no dataframe
```{r}
## Aqui vamos criar uma nova coluna no dataframe, contendo apenas o
## valor 0
notas$media <- 0 # note que aqui será usada a regra da reciclagem, ou
# seja, o valor zero será repetido até completar todas
# as linhas do dataframe
## Estrutura de repetição para calcular a média
for(i in 1:30){
## Aqui, cada linha i da coluna media sera substituida pelo
## respectivo valor da media caculada
notas$media[i] <- sum(notas[i, c("prova1", "prova2", "prova3")])/3
}
## Confere os resultados
head(notas)
```
Agora podemos melhorar o código, tornando-o mais **genérico**. Dessa
forma fica mais fácil fazer alterações e procurar erros. Uma forma de
melhorar o código acima é generalizando alguns passos.
```{r }
## Armazenamos o número de linhas no dataframe
nlinhas <- nrow(notas)
## Identificamos as colunas de interesse no cálculo da média, e
## armazenamos em um objeto separado
provas <- c("prova1", "prova2", "prova3")
## Sabendo o número de provas, fica mais fácil dividir pelo total no
## cálculo da média
nprovas <- length(provas)
## Cria uma nova coluna apenas para comparar o cálculo com o anterior
notas$media2 <- 0
## A estrutura de repetição fica
for(i in 1:nlinhas){
notas$media2[i] <- sum(notas[i, provas])/nprovas
}
## Confere
head(notas)
identical(notas$media, notas$media2)
```
Ainda podemos melhorar (leia-se: **otimizar**) o código, se utilizarmos
funções prontas do R. No caso da média isso é possível pois a função
`mean()` já existe. Em seguida veremos como fazer quando o cálculo que
estamos utilizando não está implementado em nenhuma função pronta do R.
```{r }
## Cria uma nova coluna apenas para comparação
notas$media3 <- 0
## A estrutura de repetição fica
for(i in 1:nlinhas){
notas$media3[i] <- mean(as.numeric(notas[i, provas]))
}
## Confere
head(notas)
## A única diferença é que aqui precisamos transformar cada linha em um
## vetor de números com as.numeric(), pois
notas[1, provas]
## é um data.frame:
class(notas[1, provas])
```
No caso acima vimos que não era necessário calcular a média através
de `soma/total` porque existe uma função pronta no R para fazer esse
cálculo. Mas, e se quisessemos, por exemplo, calcular a Coeficiente de
Variação (CV) entre as notas das três provas de cada aluno? Uma busca
por
```{r eval=FALSE}
help.search("coefficient of variation")
```
não retorna nenhuma função (dos pacotes básicos) para fazer esse
cálculo. O motivo é simples: como é uma conta simples de fazer não há
necessidade de se criar uma função extra dentro dos pacotes. No entanto,
nós podemos criar uma função que calcule o CV, e usá-la para o nosso
propósito
```{r}
cv <- function(x){
desv.pad <- sd(x)
med <- mean(x)
cv <- desv.pad/med
return(cv)
}
```
<div class="alert alert-warning">
NOTA: na função criada acima o único argumento que usamos foi `x`, que
neste caso deve ser um vetor de números para o cálculo do CV. Os
argumentos colocados dentro de `function()` devem ser apropriados para
o propósito de cada função.
</div>
Antes de aplicar a função dentro de um `for()` devemos testá-la para ver
se ela está funcionando de maneira correta. Por exemplo, o CV para as
notas do primeiro aluno pode ser calculado "manualmente" por
```{r }
sd(as.numeric(notas[1, provas]))/mean(as.numeric(notas[1, provas]))
```
E através da função, o resultado é
```{r }
cv(as.numeric(notas[1, provas]))
```
o que mostra que a função está funcionando corretamente, e podemos
aplicá-la em todas as linhas usando a repetição
```{r }
## Cria uma nova coluna para o CV
notas$CV <- 0
## A estrutura de repetição fica
for(i in 1:nlinhas){
notas$CV[i] <- cv(as.numeric(notas[i, provas]))
}
## Confere
head(notas)
```
Podemos agora querer calcular as médias ponderadas para as provas. Por
exemplo:
- Prova 1: peso 3
- Prova 2: peso 3
- Prova 3: peso 4
Usando a fórmula:
$$
\bar{x} = \frac{1}{N} \sum_{i=1}^{n} x_i \cdot w_i
$$
onde $w_i$ são os pesos, e $N = \sum_{i=1}^{n} w_i$ é a soma dos pesos.
Como já vimos, criar uma função é uma forma mais prática (e elegante)
de executar determinada tarefa, vamos criar uma função que calcule as
médias ponderadas.
```{r}
med.pond <- function(notas, pesos){
## Multiplica o valor de cada prova pelo seu peso
pond <- notas * pesos
## Calcula o valor total dos pesos
peso.total <- sum(pesos)
## Calcula a soma da ponderação
sum.pond <- sum(pond)
## Finalmente calcula a média ponderada
saida <- sum.pond/peso.total
return(saida)
}
```
Antes de aplicar a função para o caso geral, sempre é importante testar
e conferir o resultado em um caso menor. Podemos verificar o resultado
da média ponderada para o primeiro aluno
```{r }
sum(notas[1, provas] * c(3, 3, 4))/10
```
e testar a função para o mesmo caso
```{r }
med.pond(notas = notas[1, provas], pesos = c(3, 3, 4))
```
Como o resultado é o mesmo podemos aplicar a função para todas as linhas
através do `for()`
```{r }
## Cria uma nova coluna para a média ponderada
notas$MP <- 0
## A estrutura de repetição fica
for(i in 1:nlinhas){
notas$MP[i] <- med.pond(notas = notas[i, provas], pesos = c(3, 3, 4))
}
## Confere
head(notas)
```
<div class="alert alert-warning">
NOTA: uma função para calcular a média ponderada já existe
implementada no R. Veja `?weighted.mean()` e confira os resultados
obtidos aqui.
</div>
Repare na construção da função acima: agora usamos dois argumentos,
`notas` e `pesos`, pois precisamos dos dois vetores para calcular a média
ponderada. Repare também que ambos argumentos não possuem um valor
padrão. Poderíamos, por exemplo, assumir valores padrão para os pesos, e
deixar para que o usuário mude apenas se achar necessário.
```{r }
## Atribuindo pesos iguais para as provas como padrão
med.pond <- function(notas, pesos = rep(1, length(notas))){
## Multiplica o valor de cada prova pelo seu peso
pond <- notas * pesos
## Calcula o valor total dos pesos
peso.total <- sum(pesos)
## Calcula a soma da ponderação
sum.pond <- sum(pond)
## Finalmente calcula a média ponderada
saida <- sum.pond/peso.total
return(saida)
}
```
Repare que neste caso, como os pesos são iguais, a chamada da função sem
alterar o argumento `pesos` gera o mesmo resultado do cálculo da média
comum.
```{r }
## Cria uma nova coluna para a média ponderada para comparação
notas$MP2 <- 0
## A estrutura de repetição fica
for(i in 1:nlinhas){
notas$MP2[i] <- med.pond(notas = notas[i, provas])
}
## Confere
head(notas)
```
### Exercícios {-}
1. Escreva um loop for que percorre os números de 1 a 700 e imprime o cubo de cada número.
1. Escreva um loop for que percorre os nomes das colunas do conjunto de dados iris e imprime cada um junto com o número de caracteres na coluna nomes entre parênteses. Example de output Sepal.Length (12). Dica: Use as seguintes funções print(), paste0() e nchar().
1. Escreva um loop while que imprime o erro padrão de amostras da distribuição
normal padrão (use rnorm()) e para (break) se o erro padrão obtido for maior que 1.
1. Usando o comando next adapte o loop do exercício (3) para que números menores que 0.75 não sejam mostrados.
1. Use um loop for para simular o lançamento de uma moeda (1 - cara, 0 - coroa) e armazene os resultados em um vetor pré-especificado.
## Estrutura de seleção `if()`
Uma estrutura de seleção serve para executar algum comando apenas se
alguma condição (em forma de **expressão condicional**) seja satisfeita.
Geralmente é utilizada dentro de um `for()`.
No exemplo inicial poderíamos querer imprimir um resultado caso
satisfaça determinada condição. Por exemplo, se o valor de `x` for menor
ou igual a 105, então imprima um texto informando isso.
```{r}
x <- 100:200
for(j in 1:10){
if(x[j] <= 105){
print("Menor ou igual a 105")
}
}
```
Mas também podemos considerar o que aconteceria caso contrário. Por
exemplo, se o valor de `x`for maior do que 105, então imprima outro
texto.
```{r}
x <- 100:200
for(j in 1:10){
if(x[j] <= 105){
print("Menor ou igual a 105")
} else{
print("Maior do que 105")
}
}
```
A sintaxe será sempre no formato:
```{r, eval=FALSE}
if(<condição>){
<comandos que satisfazem a condição>
} else{
<comandos que não satisfazem a condição>
}
```
Como vimos acima, a especificação do `else{}` não é obrigatória.
Voltando ao exemplo das notas, podemos adicionar uma coluna com a
condição do aluno: `aprovado` ou `reprovado` de acordo com a sua nota.
Para isso precisamos criar uma condição (nesse caso se a nota é maior do
que 7), e verificar se ela é verdadeira.
```{r}
## Nova coluna para armazenar a situacao
notas$situacao <- NA # aqui usamos NA porque o resultado será um
# caracter
## Estrutura de repetição
for(i in 1:nlinhas){
## Estrutura de seleção (usando a média ponderada)
if(notas$MP[i] >= 7){
notas$situacao[i] <- "aprovado"
} else{
notas$situacao[i] <- "reprovado"
}
}
## Confere
head(notas)
```
## O modo R: vetorização
As funções vetorizadas do R, além de facilitar e resumir a execução de
tarefas repetitivas, também são computacionalmente mais eficientes,
*i.e.* o tempo de execução das rotinas é menor.
Já vimos que a **regra da reciclagem** é uma forma de vetorizar cálculos
no R. Os cálculos feitos com funções vetorizadas (ou usando a regra de
reciclagem) são muito mais eficientes (e preferíveis) no R. Por exemplo,
podemos criar um vetor muito grande de números e querer calcular o
quadrado de cada número. Se pensássemos em usar uma estrutura de
repetição, o cálculo seria o seguinte:
```{r}
## Vetor com uma sequência de 1 a 1.000.000
x <- 1:1000000
## Calcula o quadrado de cada número da sequência em x usando for()
y1 <- numeric(length(x)) # vetor de mesmo comprimento de x que vai
# receber os resultados
for(i in 1:length(x)){
y1[i] <- x[i]^2
}
```
Mas, da forma vetorial e usando a regra da reciclagem, a mesma operação
pode ser feita apenas com
```{r}
## Calcula o quadrado de cada número da sequência em x usando a regra da
## reciclagem
y2 <- x^2
## Confere os resultados
identical(y1, y2)
```
Note que os resultados são exatamente iguais, mas então porque se
prefere o formato vetorial? Primeiro porque é muito mais simples de
escrever, e segundo (e principalmente) porque a forma vetorizada é
muito mais **eficiente computacionalmente**. A eficiência computacional
pode ser medida de várias formas (alocação de memória, tempo de
execução, etc), mas apenas para comparação, vamos medir o tempo de
execução destas mesmas operações usando o `for()` e usando a regra da
reciclagem.
```{r}
## Tempo de execução usando for()
y1 <- numeric(length(x))
st1 <- system.time(
for(i in 1:length(x)){
y1[i] <- x[i]^2
}
)
st1
## Tempo de execução usando a regra da reciclagem
st2 <- system.time(
y2 <- x^2
)
st2
```
Olhando o resultado de `elapsed`, que é o tempo total de execução de uma
função medido por `system.time()`, notamos que usando a regra da
reciclagem, o cálculo é aproximadamente
$`r st1[3]`/`r st2[3]` = `r round(st1[3]/st2[3], 2)`$ vezes mais rápido.
Claramente esse é só um exemplo de um cálculo muito simples. Mas em
situações mais complexas, a diferença entre o tempo de execução das duas
formas pode ser muito maior.
<div class="panel panel-primary">
<div class="panel-heading">Uma nota de precaução</div>
<div class="panel-body">
Existem duas formas básicas de tornar um loop `for` no R mais rápido:
1. Faça o máximo possível fora do loop.
2. Crie um objeto com tamanho suficiente para armazenar *todos* os
resultados do loop **antes** de executá-lo.
Veja este exemplo:
```{r}
## Vetor com uma sequência de 1 a 1.000.000
x <- 1:1000000
## Cria um objeto de armazenamento com o mesmo tamanho do resultado
st1 <- system.time({
out <- numeric(length(x))
for(i in 1:length(x)){
out[i] <- x[i]^2
}
})
st1
## Cria um objeto de tamanho "zero" e vai "crescendo" esse vetor
st2 <- system.time({
out <- numeric(0)
for(i in 1:length(x)){
out[i] <- x[i]^2
}
})
st2
```
Essa simples diferença gera um aumento de tempo de execução da segunda
forma em aproximadamente
`r st2[3]`/`r st1[3]` = `r round(st2[3]/st1[3], 2)` vezes. Isso acontece
porque, da segunda forma, o vetor `out` precisa ter seu tamanho
aumentado com um elemento a cada iteração. Para fazer isso, o R precisa
encontrar um espaço na memória que possa armazenar o objeto maior. É
necessário então copiar o vetor de saída e apagar sua versão anterior
antes de seguir para o próximo loop. Ao final, foi necessário escrever
um milhão de vezes na memória do computador.
Já no primeiro caso, o tamanho do vetor de armazenamento nunca muda, e a
memória para esse vetor já foi alocada previamente, de uma única vez.
```{r, echo=FALSE, out.width='90%'}
knitr::include_graphics("img/R_club.jpg")
```
</div>
</div>
Voltando ao exemplo das notas, por exemplo, o cálculo da média simples
poderia ser feito diretamente com a função `apply()`
```{r }
notas$media.apply <- apply(X = notas[, provas], MARGIN = 1, FUN = mean)
head(notas)
```
As médias ponderadas poderiam ser calculadas da mesma forma, e usando a
função que criamos anteriormente
```{r }
notas$MP.apply <- apply(X = notas[, provas], MARGIN = 1, FUN = med.pond)
head(notas)
```
Mas note que como temos o argumento `pesos` especificado com um padrão,
devemos alterar na própria função `apply()`
```{r }
notas$MP.apply <- apply(X = notas[, provas], MARGIN = 1,
FUN = med.pond, pesos = c(3, 3, 4))
head(notas)
```
<div class="alert alert-warning">
NOTA: veja que isso é possível devido à presença do argumento `...` na
função `apply()`, que permite passar argumentos de outras funções
dentro dela.
</div>
Também poderíamos usar a função `weighted.mean()` implementada no R
```{r}
notas$MP2.apply <- apply(X = notas[, provas], MARGIN = 1,
FUN = weighted.mean, w = c(3, 3, 4))
head(notas)
```
O Coeficiente de Variação poderia ser calculado usando nossa função
`cv()`
```{r}
notas$CV.apply <- apply(X = notas[, provas], MARGIN = 1, FUN = cv)
head(notas)
```
Finalmente, a estrutura de repetição `if()` também possui uma forma
vetorizada através da função `ifelse()`. Essa função funciona da
seguinte forma:
```{r, eval=FALSE}
ifelse(<condição>, <valor se verdadeiro>, <valor se falso>)
```
Dessa forma, a atribuição da situação dos alunos poderia ser feita da
seguinte forma:
```{r}
notas$situacao2 <- ifelse(notas$MP >= 7, "aprovado", "reprovado")
head(notas)
```
### Exercícios {-}
1. Faça uma função usando loop for que recebe duas matrizes de mesma dimensão e retorna a soma das matrizes. Note que é necessário verificar se as matrizes fornecidas pelo usuário podem ser somadas, caso contrário retorne uma mensagem de erro dizendo que as matrizes não podem ser somadas.
1. Faça uma função usando loop for para multiplicar duas matrizes compatíveis. Note que é necessário verificar se as matrizes fornecidas pelo usuário podem ser multiplicadas, caso contrário retorne uma mensagem de erro dizendo que as matrizes não podem ser multiplicadas.
1. Faça uma função para resolver sistemas lineares 2 x 2 usando o método de decomposição de Gauss. Veja esta página se você não conhece o método https://matrixcalc.org/pt/slu.html.
1. Faça uma função que encontra o máximo de uma função fornecida pelo usuário em um intervalo e precisão pré-determinado pelo usuário.
1. Faça uma função que resolve uma equação não-linear de um único parâmetro em um intervalo e precisão pré-determinado pelo usuário.
## Outras estruturas: while e repeat
O `while` executa comandos enquanto uma determinada condição permanece
verdadeira.
```{r}
## Calcule a soma em 1,2,3... até que o soma seja maior do que 1000
n <- 0
soma <- 0
while(soma <= 1000){
n <- n + 1
soma <- soma + n
}
soma
```
O `repeat` é ainda mais básico, e irá executar comandos até que você
explicitamente pare a execução com o comando `break`.
```{r}
## Mesmo exemplo
n <- 0
soma <- 0
repeat{
n <- n + 1
soma <- soma + n
if(soma > 1000) break
}
soma
```
### Exercícios {-}
1. Crie uma função que retorna o absoluto de um vetor de inteiros.
1. Crie uma função em R que retorna o maior valor em um vetor de elementos númericos.
1. Crie uma função que retorna o número de valores maiores que a média de um vetor. Você pode usar a função mean().
1. Crie uma função que dado um vetor de tamanho 3 retorna os seus valores em ordem crescente e decrescente.
1. Crie uma função que calcula o fatorial de um número.