-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.Rmd
346 lines (275 loc) · 12.9 KB
/
README.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
---
output: github_document
title: eye
bibliography: eye.bib
link-citations: yes
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
library(eye)
```
<!-- badges: start -->
<!-- [![Travis build status](https://travis-ci.com/tjebo/eye.svg?branch=master)](https://www.travis-ci.com/tjebo/eye) -->
<!-- badges: end -->
See more with *eye*
## Purpose
*eye* is dedicated to facilitate very common tasks in ophthalmic research.
## Features
- [Handling of visual acuity notations](#visual-acuity)
- [Super easy count of subjects and eyes](#count-subjects-and-eyes), with smooth integration in your rmarkdown report
- [Recode your eye variable](#recoding-the-eye-variable)
- Reshape your eye data - [long](#myop) or [wide](#hyperop)
- [Quick summary of your eye data](#blink)
- [Get common summary statistics](#reveal)
- [Calculate age](#getage)
- [Clean NA equivalent entries](#clean-na-entries)
## Installation
You can install eye [from CRAN](https://CRAN.R-project.org/package=eye) using `install.packages("eye")`
Or you can install the development version from github:
```
# install.packages("devtools")
devtools::install_github("tjebo/eye")
```
I recommend to also get [eyedata](https://github.com/tjebo/eyedata/), a package collating open source ophthalmic data sets.
## Details and examples
### Visual acuity
Pesky visual acuity notations are now a matter of the past. Convert between any of Snellen (meter/ feet/ decimal!), logMAR and ETDRS.
The notation will be detected automatically and converted to the desired notation. For some more details see [VA conversion](#va-conversion). For entries with mixed notation, use `va_mixed` instead.
You can also decide to simply "clean" your VA vector with `cleanVA(x)`. This will remove all entries that are certainly no VA.
#### Examples
```{r va}
x <- c(23, 56, 74, 58) ## ETDRS letters
to_logmar(x) # wrapper of va(x, to = "logmar")
## ... or convert to snellen
to_snellen(x)
## eye knows metric as well
to_snellen(x, type = "m")
## And the decimal snellen notation, so much loved in Germany
to_snellen(x, type = "dec")
## Remove weird entries and implausible entries depending on the VA choice
x <- c("NLP", "0.8", "34", "3/60", "2/200", "20/50", " ", ".", "-", "NULL")
to_snellen(x)
to_snellen(x, from = "snellendec")
to_snellen(x, from = "etdrs")
to_snellen(x, from = "logmar")
## "plus/minus" entries are converted to the most probable threshold (any spaces allowed)
x <- c("20/200 - 1", "6/6-2", "20/50 + 3", "6/6-4", "20/33 + 4")
to_logmar(x)
## or evaluating them as logmar values (each optotype equals 0.02 logmar)
to_logmar(x, smallstep = TRUE)
## or you can also decide to completely ignore them (converting them to the nearest snellen value in the VA chart, or if you convert to logMAR, rounding to the first digit)
to_snellen(x, noplus = TRUE)
# terribly mixed notations
x <- c(NA, "nlp", 1:2, 1.1, -1, "20/40", "4/6", "6/1000", 34)
va_mixed(x, to = "snellen")
# "I only have snellen and snellen decimal notation in my data"
va_mixed(x, to = "snellen", possible = c("snellen", "snellendec"))
# "I have snellen, logmar and etdrs in my data, and there is no etdrs value less than 4"
va_mixed(x, to = "snellen", possible = c("snellen", "logmar", "etdrs"))
```
### Count subjects and eyes
This is a massive convenience function to count subjects and eyes. Because this essentially returns a list, the stored data can easily be accessed by subsetting (e.g., with `$`). You can get the subject IDs for each subset with `details = TRUE`.
```{r eyes}
library(eyedata)
eyes(amd2)
eyes(amd2)$right
eyes(amd2, details = TRUE)
head(eyes(amd2, details = TRUE)$id$right)
```
#### Smooth integration into rmarkdown
`eyestr` was designed with the use in rmarkdown in mind, most explicitly for the use inline. You can change the way numbers are converted to english with the `english` argument. By default, numbers smaller than or equal to 12 will be real English, all other numbers will be ... numbers. You can capitalise the first number with the `caps` argument.
| rmarkdown code | results in |
|----------|---------|
|We analyzed `` `r knitr::inline_expr("eyestr(amd2)")` `` |We analyzed `r eyestr(amd2)`|
|We analyzed `` `r knitr::inline_expr("eyestr(head(amd2, 100))")` ``| We analyzed `r eyestr(head(amd2, 100))`|
|We analyzed `` `r knitr::inline_expr('eyestr(amd2, english = "all")')` `` |We analyzed `r eyestr(amd2, english = "all")`|
|`` `r knitr::inline_expr("eyestr(head(amd2, 100), caps = TRUE)")` `` were analyzed | `r eyestr(head(amd2, 100), caps = TRUE)` were analyzed|
|We analyzed `` `r knitr::inline_expr('eyestr(head(amd2, 100), english = "none")')` ``| We analyzed `r eyestr(head(amd2, 100), english = "none")`|
### Recoding the eye variable
Makes recoding eye variables very easy. It deals with weird missing entries like `"."` and `""`, or `"N/A"`
```{r}
x <- c("r", "re", "od", "right", "l", "le", "os", "left", "both", "ou")
recodeye(x)
## chose the resulting codes
recodeye(x, to = c("od", "os", "ou"))
## Numeric codes 0:1/ 1:2 are recognized
x <- 1:2
recodeye(x)
## with weird missing values
x <- c(1:2, ".", NA, "", " ")
recodeye(x)
## If you are using different strings to code for eyes, e.g., you are using a different language, you can change this either with the "eyestrings" argument
french <- c("OD", "droit", "gauche", "OG")
recodeye(french, eyestrings = list(r = c("droit", "od"), l = c("gauche", "og")))
## or change it more globally with `set_eye_strings`
set_eye_strings(right = c("droit", "od"), left = c("gauche", "og"))
recodeye(french)
# to restore the default, call set_eye_strings empty
set_eye_strings()
```
### Clean NA entries
```{r}
x <- c("a", " ", ".", "-", "NULL")
tidyNA(x)
# in addition to the default strings, a new string can be added
tidyNA(x, string = "a")
# or just remove the strings you want
tidyNA(x, string = "a", defaultstrings = FALSE)
```
### reveal
Show common statistics for all numeric columns, for the entire cohort or aggregated by group(s):
```{r stats, warning=FALSE, message=FALSE}
reveal(iris)
reveal(iris, by = "Species") #can be several groups
```
### getage
- Calculate age in years, as [periods or durations](https://lubridate.tidyverse.org/articles/lubridate.html#time-intervals)
```{r age, warning=FALSE, message=FALSE}
dob <- c("1984-10-16", "2000-01-01")
## If no second date given, the age today
getage(dob)
getage(dob, "2000-01-01")
```
### myop
Often enough, there are right eye / left eye columns for more than one variable, e.g., for both IOP and VA. This may be a necessary data formal for specific questions. However, "eye" is also variable (a dimension of your observation), and it can also be stored in a separate column. The data would be "longer".
Indeed, R requires exactly this data shape for many tasks: "eye[r/l]" as a separate column, and each eye-related variable (e.g., IOP or VA) in their own dedicated column.
`myop` provides an easy to use API for an automatic reshape of your data to a "myop" format.
```{r myop}
## Simple data frame with one column for right eye and left eye.
iop_wide <- data.frame(id = letters[1:3], iop_r = 11:13, iop_l = 14:16)
iop_wide
```
```{r}
myop(iop_wide)
```
Or another example with many more variables:
<details>
<summary>Click to unfold code to create `wide_df` </summary>
```{r messy df}
wide_df <- data.frame(
id = letters[1:4],
surgery_right = c("TE", "TE", "SLT", "SLT"),
surgery_left = c("TE", "TE", "TE", "SLT"),
iop_r_preop = 21:24, iop_r_postop = 11:14,
iop_l_preop = 31:34, iop_l_postop = 11:14,
va_r_preop = 41:44, va_r_postop = 45:48,
va_l_preop = 41:44, va_l_postop = 45:48
)
```
</details>
```{r eval=FALSE, include = FALSE}
# library(flextable)
# wide_df %>%
# flextable() %>%
# autofit() %>%
# save_as_image("messy.png", zoom = 3, expand = 10, webshot = "webshot")
```
<img src="man/figures/messy.png"/>
```{r myop two vars}
myop_df <- myop(wide_df)
myop_df
```
### hyperop
If you actually need certain eye-related variables spread over two columns, `hyperop()` is your friend:
```{r}
hyperop(myop(iop_wide), iop)
hyperop(myop_df, cols = matches("va|iop"))
```
### blink
See your data in a blink of an eye - wrapper around [`myop`](#myop), [`eyes`](#eyes), [`va`](#va) and [`reveal`](#reveal). It will look for VA and for IOP columns and provide the summary stats for the entire cohort and for right and left eyes for each variable.
[**This requires a certain format of your names and codes**](#names-and-codes)
```{r, eval = FALSE}
blink(wide_df)
```
## Names and codes
**eye works smoother with tidy data** (any package does, really!)
An important part of tidy data are good names. [Learn more about tidy data.](https://tidyr.tidyverse.org/articles/tidy-data.html)
### Tips and rules for naming:
1) Don't be too creative with your names!
2) Use common coding:
- **eyes**: "r", "re", "od", "right" - or numeric coding r:l = 0:1 or 1:2
- **Visual acuity**: "VA", "BCVA", "Acuity"
- **Intraocular pressure**: "IOP", "GAT", "NCT", "pressure"
- **Patient identifier**: "pat", "patient", "ID" (ideally both: "patientID" or "patID")
3) Column names:
- No spaces!
- Do not use numeric coding for eyes in column names
- Separate eye and VA and IOP codes with underscores ("bcva_l_preop", "VA_r", "left_va", "IOP_re")
- Keep names short
- Don't use underscores when you don't need to: Consider each section divided by an underscore as a relevant characteristic of your variable. E.g., "preop" instead of "pre_op", or simply "VA" instead of "VA_ETDRS_Letters"
### Name examples
Good names (`eye` will work nicely)
```{r}
## right and left eyes have common codes
## information on the tested dimension is included ("iop")
## VA and eye strings are separated by underscores
## No unnecessary underscores.
names(wide_df)
names(iop_wide)
```
OK names (`eye` will work)
```{r}
## Id and Eye are common names, there are no spaces
## VA is separated from the rest with an underscore
## BUT:
## The names are quite long
## There is an unnecessary underscore (etdrs are always letters). Better just "VA"
c("Id", "Eye", "FollowupDays", "BaselineAge", "Gender", "VA_ETDRS_Letters",
"InjectionNumber")
## All names are commonly used (good!)
## But which dimension of "r"/"l" are we exactly looking at?
c("id", "r", "l")
```
Bad names (`eye` will fail)
```{r}
## VA/IOP not separated with underscore
## `eye` won't be able to recognize IOP and VA columns
c("id", "iopr", "iopl", "VAr", "VAl")
## A human may think this is clear
## But `eye` will fail to understand those variable names
c("person", "goldmann", "vision")
## Not even clear to humans
c("var1", "var2", "var3")
```
### How do I rename columns in R?
When I started with R, I found it challenging to rename columns and I found the following threads on stackoverflow very helpful:
- [Rename single column](https://stackoverflow.com/q/7531868/7941188)
- [Rename columns with named vector](https://stackoverflow.com/q/20987295/7941188)
I find the two following methods straight forward:
```{r}
# I've got a data frame with unfortunate names:
name_mess <- data.frame(name = "a", oculus = "r", eyepressure = 14, vision = 0.2)
names(name_mess)
## rename all names
names(name_mess) <- c("patID", "eye", "IOP", "VA")
names(name_mess)
```
```{r, include=FALSE}
names(name_mess) <- c("name", "oculus", "eyepressure", "vision")
```
```{r}
## To rename only specific columns, even if you are not sure about their exact position:
names(name_mess)[names(name_mess) %in% c("name", "vision")] <- c("patID", "VA")
names(name_mess)
```
## Important notes
**I do not assume responsability for your data or analysis**. Please always keep a critical mind when working with data - if you do get results that seem implausible, there may be a chance that the data is in an unfortunate shape for which `eye` may not be suitable.
## VA conversion
- VA conversion between Snellen, ETDRS and logMAR is based on charts and formulas in [@holladay], [@beck] and [@gregori]
- Categories **counting fingers** and **hand movements** are converted following [@bach]
- Categories **(no) light perception** are converted following the suggestions by Michael Bach
## Acknowledgements
- Thanks to **Alasdair Warwick**, **Aaron Lee**, **Tim Yap**, **Siegfried Wagner** and **Abraham Olvera** for great suggestions, testing and code review.
- **Pearse Keane**, **Dun Jack Fu**, **Katrin Fasler** and **Christoph Kern** for their contribution of open source data
- Thanks to [Antoine Fabri](https://github.com/moodymudskipper) for his contribution to `getage()`
- Thanks to Hadley Wickham and all developers of the `tidyverse` packages and the packages `roxygen2`, `usethis`, `testthis` and `devtools`, all on which `eye` heavily relies.
## Resources
- [Michael Bach's homepage](https://michaelbach.de/sci/acuity.html)
- [Michael Bach on NLP and LP](https://michaelbach.de/sci/pubs/Bach2007IOVS_eLetter_FrACT.pdf)
## References