18  Pseudocoding

In the previous chapter, we learned about using artificial intelligence to help us write R code. After seeing the power of AI, and particularly agentic AI, you might ask why we need to learn code at all. Let’s just hand our problem to our favourite AI tool and they can write the code for us. I agree up to a point.

We really don’t need to remember the finer details of coding. For example, we don’t need to remember whether the function is called mutate() or mutation(), and or whether we specify it as lab or labs. However, we still want to understand the structure of code. There are a few reasons for this. First, it allows us to know what we can ask of AI. Second, it allows us to read code that we did not write and understand what it does. Third, sometimes it will be easier for us to modify code that AI has written for us.

What we don’t need to do is write perfect code from scratch.

One way to ensure that we know the proper structure of code is to write pseudocode. Pseudocode is a way of writing out the steps of your code in plain English, without worrying about the syntax of R. This can help you think through the logic of your code – and on a test can demonstrate that you understand the structure of code. It also helps you provide detailed instructions to AI about what you want.

So what is pseudocode? Consider the following three previous test questions. For each problem, I have supplied the R code, and the pseudocode that describes the steps of the code.

A pipeline from Module 2

NoteThe problem

Question 35 from the Module 2 bank. This question uses rm_yields_1990_2025.csv.

(a) In one pipeline: filter to Flax from 2020 through 2025, compute each RM’s mean_yield and number of observations, retain RMs with at least four observations, and sort from highest mean to lowest. Save the result as flax_summary.

(b) Report the top RM, its mean, and its count.

(c) In a comment: why does the minimum-count rule improve the ranking, and why must filter(n >= 4) come after summarise()?

ImportantThe R code
flax_summary <- rm_yields |>
  filter(Crop == "Flax" & Year >= 2020) |>
  group_by(RM) |>
  summarise(mean_yield = mean(Yield),
            n = n()) |>
  filter(n >= 4) |>
  arrange(desc(mean_yield))
TipThe pseudocode
read rm_yields_1990_2025.csv   (one row per RM, year and crop)
  keep flax observations from 2020 through 2025
  for each RM, calculate its average yield and its number of observations
  keep only the RMs with at least four observations
  sort from highest average to lowest
save the table as flax_summary

The result should be a data frame with one row per qualifying RM, and
columns for the RM, its average yield and its number of observations.

Report the RM, average and count in the first row.

Part (c) is the part that is actually hard. The summarising line is where the data stops being one row per observation and starts being one row per RM, and it is the line that creates n. A filter on n has to come after it, because before it there is no such column. The minimum-count rule matters because an RM with one good year would otherwise sit at the top of the ranking on the strength of a single number.

Merging from Module 3

NoteThe problem

Question 31 from the Module 3 bank. The file q31_fields.csv has one row per field, with the harvest year, the crop, the acres and the yield. The file q31_prices.csv gives the price per bushel for each crop in each year.

(a) Join the prices onto the fields, matching on crop and year. Report the row counts before and after.

(b) In a comment: what happens if you join on crop alone, and why is the result wrong?

(c) Compute the total revenue for the farm.

ImportantThe R code
priced <- fields |>
  left_join(prices, by = join_by(crop, year))

nrow(fields)
nrow(priced)

priced |>
  mutate(revenue = yield_bu_ac * acres * price_per_bu) |>
  summarise(total_revenue = sum(revenue))
TipThe pseudocode
read q31_fields.csv      (one row per field)
read q31_prices.csv      (one price per crop per year)

record how many rows the fields data has

merge the prices and fields data by BOTH crop and year
  check the row count again -- it should not have changed

calculate the revenue for each field: yield x acres x price

sum revenue across all fields

the result should be a single number (total farm revenue)

The answer to (b) is in the second line of the plan. A price belongs to a crop in a year, so matching on the crop alone finds two prices for every field and the twenty rows become forty. The row-count check on the next line is what catches it.

A chart from Module 4

NoteThe problem

Question 27 from the Module 4 bank. The file rm_yields_1990_2025.csv. Lentils are recorded in lb/ac; the other crops in bu/ac.

(a) Calculate the 2025 average yield across RMs of each crop except lentils (filter it out, and say why in a comment).

(b) Make a bar chart of the averages, ordered from highest to lowest.

(c) In a comment: what would the chart look like if lentils stayed in?

ImportantThe R code
avg_2025 <- rm_yields |>
  filter(year == 2025,
         crop != "Lentils") |>
  group_by(crop) |>
  summarise(mean_yield = mean(yield, na.rm = TRUE))

avg_2025 |>
  ggplot(aes(x = mean_yield, y = fct_reorder(crop, mean_yield))) +
  geom_col(fill = "DarkGreen") +
  labs(title = "Average 2025 crop yields in Saskatchewan RMs",
       x = "Yield (bu/ac)", y = NULL) +
  theme_classic(base_size = 13)
TipThe pseudocode
read rm_yields_1990_2025.csv
  keep only rows where the year is 2025, and drop lentils
  for each crop, work out the average yield across the RMs
  
call the resulting table avg_2025.  This should be a data frame with one row per crop and two columns: crop and mean_yield.

take avg_2025 and make a bar chart of the average yields in 2025
  yield on the x axis, crop on the y axis
  order the crops by their yield
  Add the following titles:
    Main title: Average 2025 crop yields in Saskatchewan RMs
    x axis: Yield (bu/ac)
    y axis should be null 
  Text size should be 13, and the bars should be dark green

The break in the middle matters. Everything above the blank line is data preparation and everything below it is drawing, and the chart can only be drawn once the table has one row per crop. Most charts that will not come out are a data problem rather than a plotting problem.

Dropping lentils is a decision rather than a step, which is why the question asks for a comment on it. Lentils are measured in pounds per acre and everything else in bushels, so a lentil bar near 1,900 would stretch the axis and squash every other crop into a sliver.

18.1 Who is the audience for pseudocode?

For your pseudocoding there are two audiences: me and AI. I will ask you to write pseudocode for your tests, in order to ensure that you understand the structure of the code. But the code can also be used to feed into AI so it knows exactly what you want done.

Getting the level of detail right. One of the big issues is how much detail to put into your pseudo code. You don’t need to name exact functions, but you do have to specify the operations that you want done. For example if you want to take the average yield for each RM:

  • “calculate the average yield” is not enough.
  • “use mean() to calculate the average yield for each RM” is more than you need, though naming a function does no harm.
  • “calculate the average yield for each RM” is just right.

For graphing, if you want specific titles or a specific look, you will need to specify that.

You should also try to name the dataset explicitly. If your folder has many different yield datasets, your AI might not know which one to choose if it is using your pseudo code as a basis for its actual code.

While you don’t need a particular format, if you follow our standard conventions around indentation and spacing, then your code will be more readable.