Last Update
Image by KIMDAEJEUNG from Pixabay

Articles

Modeling plant growth with R

What is R?

R is a powerful, free, open-source programming language and environment designed for statistical computing, data analysis, and visualization.

It’s widely used in science, ecology, and biology — making it perfect for aquarists who want to analyze water parameters, track plant growth, or model nutrient uptake.

A typical scenario where R can come in handy is when you want to model the growth curve of plants depending on external nutrient concentrations (i.e., as a function of external nutrient concentration). Let's say you have five experimental aquariums, each using a different concentration of nutrients → from the smallest to the largest. At the beginning of the experiment, you will start with plants of the same size and number (quantity) in all aquariums. At the end of the experiment, you weigh (or measure) the plants from each aquarium, giving you five data points with their weight.

You will then have the following dataset to use with R, for example:

  1. Nutrient concentrations in individual aquariums: 2, 4, 8, 16, 32 mg/ℓ NO3 (as a proxy for all other nutrients)
  2. Plant weight at the end of the experiment: 2.123, 3.791, 3.791, 4.516, 5.088 grams

You can then enter this data into R and use it to model a growth curve (see example below or the practical application in this article).

R application:

  • Handles complex datasets (e.g., nutrient levels over time).
  • Creates publication-quality graphs.
  • Supports custom mathematical models for plant growth.

How to Install R on Windows

How R Can Help Aquarist

  • Model plant growth curves to understand how light, CO2, and nutrients affect growth rate (yield).
  • Predict optimal nutrient concentrations for low, medium or maximum growth.
  • Compare different plant species or tank setups.
  • Visualize before-and-after effects of changes in fertilization or lighting.

Is R Easy to Use?

R has a learning curve, particularly for those new to programming, as it relies on script-based commands rather than a point-and-click interface. However, AI tools (like ChatGPT, Perplexity, Grok, Gemini, Copilot or DeepSeek) significantly ease this process by providing real-time guidance, explaining code, and even generating or modifying scripts tailored to your needs. Basic tasks, such as plotting data, can be learned in hours through free tutorials on platforms like DataCamp, Coursera, or the official R manual, with AI assisting to clarify concepts or troubleshoot errors instantly. For aquarists without coding experience, R might initially feel challenging, but resources like the free online book R for Data Science and AI-driven support make it far more accessible. RStudio’s interface, with auto-completion and debugging, further simplifies the process. Once familiar, R becomes intuitive for repetitive tasks, especially with pre-written scripts shared by the aquarium community that you can tweak with AI help. Overall, R is of "medium" difficulty—rewarding for data enthusiasts and made significantly more approachable with AI assistance, though not as beginner-friendly as spreadsheet tools like Excel.

Example: Modeling growth curve for Rotala macrandra 'Narrow Leaf'

Running the program

  • After installing the program, open it. You should see a program window similar to this:
    RGui (new)

    Make sure you are inside the "R Console" window.

  • Enter the example code into the program:

    You can copy and paste the example below into the R Console:

    # Create graph in R that plots my data points and overlays the regression curve based on my custom formula:
    
    # ----- User-defined inputs -----
    x <- c(2, 4, 8, 16, 32)
    y <- c(2.123, 3.791, 3.791, 4.516, 5.088)
    
    xlab <- "Nutrient concentration (mg/L NO3)"    # x-axis label
    ylab <- "Yield (grams of fresh weight)"        # y-axis label
    main_title <- "Rotala macrandra 'Narrow Leaf'" # plot title
    
    x_tick_by <- 2
    y_tick_by <- 1
    # y_tick_by <- 5
    
    model_name_text <- "(Model: Rational)"
    
    # Define your custom regression function here:
    regression_func <- function(x) {
    	(-27977905.393401 + 27713305.692703 * x) / (1 + 6174159.487309 * x + (-27198.019768 * x^2))
    }
    
    # Define formula text once here as an expression
    formula_text <- expression(paste(y == frac(-27977905.393401 + 27713305.692703 * x, 1 + 6174159.487309 * x + -27198.019768 * x^2)))
    # -------------------------------
    
    plot_regression <- function() {
    	y_hat <- regression_func(x)
    	SSE <- sum((y - y_hat)^2)
    	SST <- sum((y - mean(y))^2)
    	R2 <- 1 - SSE / SST
    
    	x_seq <- seq(0, max(x), length.out = 200)
    	y_pred <- regression_func(x_seq)
    
    	x_ticks <- seq(0, max(x_seq), by = x_tick_by)
    	y_ticks <- seq(0, max(c(y, y_pred)) + y_tick_by, by = y_tick_by)
    
    	# If you what to remove labels and main title (else comment this section):
    	  # Reduce plot margins (bottom, left, top, right)
    	  par(mar = c(2, 2, 1, 1))  # default is usually c(5, 4, 4, 2) + 0.1
    	  # Optionally remove outer margins too
    	  par(oma = c(0, 0, 0, 0))
    
    	# Now plot with empty axis labels and titles to maximize plot area
    	plot(x, y, pch = 19, cex = 2, 
    		# If you what to keep labels and main title:
    			# xlab = xlab, ylab = ylab, main = main_title,
    		# If you what to remove labels and main title, set them empty:
    			xlab = "", ylab = "", main = "",
    	   xaxt = "n", yaxt = "n", 
    	   ylim = c(min(y_ticks), max(y_ticks)), xlim = c(min(x_ticks), max(x_ticks)))
    
    	# Add custom axes and grids as before if needed, e.g.:
    	axis(side = 1, at = x_ticks)
    	axis(side = 2, at = y_ticks)
    
    	abline(h = y_ticks, col = "lightgray", lty = "dotted")
    	abline(v = x_ticks, col = "lightgray", lty = "dotted")
    
    	# Regression curve
    	lines(x_seq, y_pred, col = "red", lwd = 2)
    
    	# GREEN intersection (Km): x = KmConc, y = KmYield (see http://localhost/golias/abc/1_experiments/exp2.php)
    	segments(x0 = 2.284, y0 = 0, x1 = 2.284, y1 = 2.530, col = "green4")
    	segments(x0 = 0, y0 = 2.530, x1 = 2.284, y1 = 2.530, col = "green4")
    	points(2.284, 2.530, col = "green4", pch = 16, cex = 1)
    	text(2.284, 2.530, labels = "Km (50%)", pos = 4, col = "green4", font = 1, offset = 0.7, cex = 1.1)
    	
    	# BLUE intersection (Optimum): x = conc75, y = yield75 (see http://localhost/golias/abc/1_experiments/exp2.php)
    	segments(x0 = 5.741, y0 = 0, x1 = 5.741, y1 = 3.795, col = "blue", lty = "dotted")
    	segments(x0 = 0, y0 = 3.795, x1 = 5.741, y1 = 3.795, col = "blue", lty = "dotted")
    	points(5.741, 3.795, col = "blue", pch = 16, cex = 1)
    	# text(5.741, 3.795, labels = "Optimum\n(75%)", pos = 4, col = "blue", font = 2, offset = 0.7, cex = 1.1)
    	text(5.741, 3.795, labels = "Optimum (75%)", pos = 4, col = "blue", font = 1, offset = 0.7, cex = 1.1)
    	
    	r2_text <- substitute(paste("R"^2, " = ", r2_val), list(r2_val = round(R2, 4)))
    
    	usr <- par("usr")
    
    	text(x = usr[2]*0.95, y = usr[3] + (usr[4]-usr[3])*0.18, labels = formula_text, adj = c(1, 0), cex = 1.1)
    	text(x = usr[2]*0.95, y = usr[3] + (usr[4]-usr[3])*0.12, labels = model_name_text, adj = c(1, 0), cex = 1.1)
    	text(x = usr[2]*0.95, y = usr[3] + (usr[4]-usr[3])*0.05, labels = r2_text, adj = c(1, 0), cex = 1.1)
    }
    
    # Display plot in R window
    plot_regression()
    
    # Save plot as PNG file with transparent background
    png(filename = "custom_regression_plot.png", width = 1000, height = 500, units = "px", bg = "transparent")
    plot_regression()
    dev.off()
    

    You should see this now:

    RGui (result)

    And in R’s current working directory (usually C:/Documents) you'll find the PNG output file:

    Custom Regression Plot

What the code does

The example script plots measured plant yields against nitrate concentration and overlays a custom regression curve.

  1. Inputs data:
    x <- c(2, 4, 8, 16, 32)  # NO3 concentration (mg/L)
    y <- c(2.123, 3.791, 3.791, 4.516, 5.088)  # Yield in grams
    
  2. Defines a regression function — a mathematical formula fitted to your data.
    For example:
    regression_func <- function(x) {
      # y = (a + bx) / (1 + cx + dx^2)
      (-27977905.393401 + 27713305.692703 * x) / (1 + 6174159.487309 * x + (-27198.019768 * x^2))
    }
    
    Or:
    regression_func <- function(x) {
      # y = ax / (b + x)
      12.266 * x / (4.270 + x)
    }
    
    Note: Finding the right regression function is crucial for modeling a growth curve, but it is also the most difficult part. I use the trial version of CurveExpert Professional (curveexpert.net) for this, but it can also be done using only R, which contains several preset regression functions (like model_saturation, model_rational_zero, model_hoerl, model_modhoerl, etc.) that can be used for this purpose, although these functions do not usually provide such good results.
  3. Plots:
    • Black dots = measured data points.
    • Red curve = predicted growth curve.
    • Green marker = nutrient level at 50% of max yield (Km).
    • Blue marker = nutrient level at ~75% of max yield (optimum).
  4. Adds R2 value (model confidence), formula, and grid lines.
  5. Saves the plot as a PNG:
    png(filename = "custom_regression_plot.png", width = 1000, height = 500, bg = "transparent")
    plot_regression()
    dev.off()
    
    • 📂 Where it’s saved: In R’s current working directory → check with getwd() ← enter this command into R Console and press Enter

How to Modify the Code

  • Change plant species: Replace x and y with your own measurements.
  • Adjust axis labels: Edit xlab and ylab.
  • Change tick spacing: Modify x_tick_by and y_tick_by.
  • Use a different model: Replace the regression_func definition with your own formula.
  • Change output size: Adjust width and height in the png() function.
R may look intimidating at first, but for aquarists it’s a powerful microscope for data — letting you see patterns in plant growth that are invisible to the naked eye. Once you have a template (like the one above), you can reuse it for any plant, nutrient, or tank condition.
Back to Top