Skip to content

Commit 7e2e985

Browse files
authored
Merge pull request #46 from carpentries-incubator/ep-5-draft
Ep 5 draft I revised and merged SS's edits. There was a missing div which I also fixed.
2 parents 0f08b6b + b18fcaf commit 7e2e985

2 files changed

Lines changed: 511 additions & 622 deletions

File tree

episodes/04-code-readability.md

Lines changed: 151 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ Let's make sure we commit our changes.
156156

157157
$ git add eva_data_analysis.R
158158
$ git commit -m "Move library calls to the top of the script"
159-
Some highlights:
159+
160+
160161
Some highlights:
161162
- Only alphanumeric characters, dot, and underscores are permitted in variable names.
162163
- Must start with a letter or a dot (.); if it starts with a dot, the next character cannot be a digit.
@@ -1032,7 +1033,7 @@ plot_cumulative_time_in_space <- function(df, graph_file) {
10321033

10331034
::::::
10341035

1035-
Finally, our code may look something like the following:
1036+
At this time, our code may look something like the following:
10361037

10371038
```r
10381039

@@ -1142,6 +1143,153 @@ plot_cumulative_time_in_space(eva_tbl, graph_file)
11421143

11431144
```
11441145

1146+
Now that we have abstracted away the code into functions, we can place them inside their own .R files and remove them from the main body of the code to further improve readability . Once the functions are tucked away in their .R files, we can `source()` them and make them available.
1147+
1148+
To accomplish this, we can create three .R scripts (one with each function) inside the `R/` folder. A common name convention is to use dashes rather than underscores for filenames. This reorganization gets us closer to having an R package that is reusable by us and others in the future. The scripts should look like the following:
1149+
1150+
The first script is ``read-json-to-dataframe.R`
1151+
```r
1152+
#read-json-to-dataframe.R
1153+
1154+
1155+
#' Read EVA data from a JSON file into a tibble
1156+
#'
1157+
#' Reads a JSON file containing an array of records (objects) and returns the
1158+
#' contents as a tibble for downstream analysis.
1159+
#'
1160+
#' @param input_file Path to a JSON file (character scalar). The file is expected
1161+
#' to contain a JSON array of objects, e.g. `[{"eva":"1", ...}, {"eva":"2", ...}]`.
1162+
#' @return A tibble with one row per JSON record and one column per field.
1163+
#' @examples
1164+
#' eva_tbl <- read_json_to_dataframe("./eva-data.json")
1165+
#' dplyr::glimpse(eva_tbl)
1166+
read_json_to_dataframe <- function(input_file) {
1167+
jsonlite::fromJSON(input_file) |>
1168+
tibble::as_tibble()
1169+
}
1170+
1171+
```
1172+
1173+
The 2nd script is `write-dataframe-to-csv.R`
1174+
```r
1175+
1176+
# write-dataframe-to-csv.R
1177+
1178+
#' Clean an EVA dataframe and write it to CSV
1179+
#'
1180+
#' Coerces key columns to the expected types (e.g., `eva` to numeric and `date`
1181+
#' to POSIXct), drops records missing a usable `duration` or `date`, writes the
1182+
#' result to a CSV file, and returns the cleaned dataframe.
1183+
#'
1184+
#' @param df A data frame or tibble containing EVA records. Expected columns
1185+
#' include `eva`, `date`, and `duration`.
1186+
#' @param output_file Path to the output CSV file (character scalar).
1187+
#'
1188+
#' @return The cleaned dataframe (same class as `df` where practical), suitable
1189+
#' for piping into downstream steps.
1190+
#'
1191+
#' @examples
1192+
#' eva_tbl <- read_json_to_dataframe("./eva-data.json")
1193+
#' eva_tbl <- write_dataframe_to_csv(eva_tbl, "./eva-data.csv")
1194+
write_dataframe_to_csv <- function(df, output_file) {
1195+
df <- df |>
1196+
dplyr::mutate(
1197+
eva = as.numeric(eva),
1198+
date = lubridate::ymd_hms(date, quiet = TRUE)
1199+
) |>
1200+
dplyr::filter(!is.na(duration), duration != "", !is.na(date))
1201+
1202+
readr::write_csv(df, output_file)
1203+
df
1204+
}
1205+
1206+
```
1207+
1208+
1209+
The last script is `plot-cumulative-time-in-space.R`
1210+
1211+
```r
1212+
1213+
# plot-cumulative-time-in-space.R
1214+
1215+
#' Plot cumulative EVA time in space and save the figure
1216+
#'
1217+
#' Computes EVA duration in hours from a `duration` string column (expected format
1218+
#' like `"H:MM"` or `"HH:MM"`), calculates cumulative time over chronological
1219+
#' `date`, generates a ggplot line chart, saves it to disk, and prints it.
1220+
#'
1221+
#' @param df A data frame or tibble containing EVA records. Expected columns:
1222+
#' `date` (POSIXct or parseable datetime) and `duration` (character `"H:MM"`).
1223+
#' @param graph_file Path to the output image file (character scalar), e.g.
1224+
#' `"./cumulative_eva_graph.png"`.
1225+
#'
1226+
#' @return Invisibly returns the ggplot object.
1227+
#'
1228+
#' @examples
1229+
#' eva_tbl <- read_json_to_dataframe("./eva-data.json") |>
1230+
#' write_dataframe_to_csv("./eva-data.csv")
1231+
#' plot_cumulative_time_in_space(eva_tbl, "./cumulative_eva_graph.png")
1232+
plot_cumulative_time_in_space <- function(df, graph_file) {
1233+
df <- df |>
1234+
dplyr::arrange(date) |>
1235+
dplyr::mutate(
1236+
duration_hours = {
1237+
parts <- stringr::str_split(duration, ":", n = 2, simplify = TRUE)
1238+
as.numeric(parts[, 1]) + as.numeric(parts[, 2]) / 60
1239+
},
1240+
cumulative_time = cumsum(duration_hours)
1241+
)
1242+
1243+
p <- ggplot2::ggplot(df, ggplot2::aes(x = date, y = cumulative_time)) +
1244+
ggplot2::geom_point() +
1245+
ggplot2::geom_line() +
1246+
ggplot2::labs(
1247+
x = "Year",
1248+
y = "Total time spent in space to date (hours)"
1249+
) +
1250+
ggplot2::theme_minimal()
1251+
1252+
ggplot2::ggsave(graph_file, plot = p, width = 9, height = 5, dpi = 300)
1253+
print(p)
1254+
1255+
invisible(p)
1256+
}
1257+
1258+
```
1259+
1260+
With these file in place, our main code block from our original script should be edited to use the `source()` function to reuse those functions.
1261+
1262+
1263+
```r
1264+
1265+
# EVA cumulative time pipeline (tidyverse-first, with reusable functions + roxygen-style docs)
1266+
1267+
library(tidyverse)
1268+
library(jsonlite)
1269+
library(lubridate)
1270+
1271+
# Files
1272+
input_file <- "./eva-data.json"
1273+
output_file <- "./eva-data.csv"
1274+
graph_file <- "./cumulative_eva_graph.png"
1275+
1276+
1277+
#these source() calls make the functions available inside this R session
1278+
1279+
source("read-json-to-dataframe.R ")
1280+
source("write-dataframe-to-csv.R")
1281+
source("plot-cumulative-time-in-space.R")
1282+
1283+
# --- Main ---
1284+
eva_tbl <- read_json_to_dataframe(input_file) |>
1285+
write_dataframe_to_csv(output_file = output_file)
1286+
1287+
plot_cumulative_time_in_space(eva_tbl, graph_file)
1288+
1289+
1290+
```
1291+
1292+
11451293
Do not forget to commit any uncommitted changes you may have and then push your work to GitHub.
11461294

11471295
```bash
@@ -1167,7 +1315,7 @@ Comments are really important. Roxygen2 can help you insert comment in a structe
11671315
### Code state
11681316

11691317
At this point, the code in your local software project's directory should be as in:
1170-
<https://github.com/carpentries-incubator/bbrs-software-project/tree/05-code-structure>
1318+
<https://github.com/carpentries-incubator/better-research-software-r/blob/main/tree/04-code-structure>
11711319

11721320
:::
11731321

0 commit comments

Comments
 (0)