diff --git a/src/core/landscape.jl b/src/core/landscape.jl
index c286108b28dc81e174ab043ec5e894aac73ad4f2..f8f8fd8dd0ae1704b4e7ed6844119079c09bd0af 100644
--- a/src/core/landscape.jl
+++ b/src/core/landscape.jl
@@ -17,6 +17,7 @@ in a single object. The model landscape consists of a matrix of pixels.
 struct Pixel
     landcover::LandCover
     fieldid::Union{Missing, UInt32}
+    #FIXME actually this is stupid - I don't want a field ID, I want a field object
 end
 
 """
diff --git a/src/core/output.jl b/src/core/output.jl
index de9047aa1670e3191a3f199119b695bc231c88c5..eb767143ae1447c41b3ea323c8b248a6f44425a1 100644
--- a/src/core/output.jl
+++ b/src/core/output.jl
@@ -5,6 +5,7 @@
 
 const LOGFILE = "simulation.log"
 const POPFILE = "populations.csv"
+const INDFILE = "individuals.csv"
 
 ## Note: `setupdatadir()` was adapted from the GeMM model by Leidinger et al.
 ## (https://github.com/CCTB-Ecomods/gemm/blob/master/src/output.jl)
@@ -55,16 +56,78 @@ function setupdatadir()
     cp(lcmap, joinpath(param("core.outdir"), basename(lcmap)), force = true)
     cp(ffmap, joinpath(param("core.outdir"), basename(ffmap)), force = true)
     # Create the data output file(s)
-    open(joinpath(param("core.outdir"), POPFILE), "w") do f
-        println(f, "Date;Species;Abundance")
+    initnaturedatafiles()
+end
+
+
+## NATURE MODEL OUTPUT FUNCTIONS
+
+#XXX Should these be moved to a separate file (src/nature/natureoutput.jl)?
+# At the moment I've kept them here so that all output functions are in the same place.
+let nextpopout = nothing, nextindout = nothing
+    """
+        initnaturedatafiles()
+
+    Initialise the files needed for the nature output data and set the
+    date for the next data output, depending on the model settings.
+    """
+    global function initnaturedatafiles()
+        startdate = param("core.startdate")
+        popoutfreq = param("nature.popoutfreq")
+        indoutfreq = param("nature.indoutfreq")
+        if popoutfreq != "never"
+            open(joinpath(param("core.outdir"), POPFILE), "w") do f
+                println(f, "Date;Species;Abundance")
+            end
+            (popoutfreq == "daily") && (nextpopout = startdate)
+            (popoutfreq == "monthly") && (nextpopout = startdate+Month(1))
+            (popoutfreq == "yearly") && (nextpopout = startdate+Year(1))
+            (popoutfreq == "end") && (nextpopout = param("core.enddate"))
+        else nextpopout = startdate - Day(1) end
+        if indoutfreq != "never"
+            open(joinpath(param("core.outdir"), INDFILE), "w") do f
+                println(f, "Date;ID;X;Y;Species;Sex;Age;Energy")
+            end
+            (indoutfreq == "daily") && (nextindout = startdate)
+            (indoutfreq == "monthly") && (nextindout = startdate+Month(1))
+            (indoutfreq == "yearly") && (nextindout = startdate+Year(1))
+            (indoutfreq == "end") && (nextindout = param("core.enddate"))
+        else nextindout = startdate - Day(1) end
+    end
+
+    """
+        recordnaturedata(model)
+
+    Record the relevant data output from the nature model, depending on the frequency settings.
+    """
+    global function recordnaturedata(model::AgentBasedModel)
+        popoutfreq = param("nature.popoutfreq")
+        indoutfreq = param("nature.indoutfreq")
+        if model.date == nextpopout
+            savepopulationdata(model)
+            (popoutfreq == "daily") && (nextpopout = model.date+Day(1))
+            (popoutfreq == "monthly") && (nextpopout = model.date+Month(1))
+            (popoutfreq == "yearly") && (nextpopout = model.date+Year(1))
+        end
+        if model.date == nextindout
+            saveindividualdata(model)
+            (indoutfreq == "daily") && (nextindout = model.date+Day(1))
+            (indoutfreq == "monthly") && (nextindout = model.date+Month(1))
+            (indoutfreq == "yearly") && (nextindout = model.date+Year(1))
+        end
     end
 end
 
+##XXX The current method of controlling the next output date for the different
+## output functions is a bit messy. If the farm model introduces similar parameters
+## to popoutfreq/indoutfreq, it would be worth revising this to a cleaner hook system.
+
 """
     savepopulationdata(model)
 
-Print a comma-separated set of lines to `populations.csv`, giving the
-current date and population size for each animal species.
+Print a comma-separated set of lines to `populations.csv`, giving the current date
+and population size for each animal species. May be called never, daily, monthly,
+yearly, or at the end of a simulation, depending on the parameter `nature.popoutfreq`.
 """
 function savepopulationdata(model::AgentBasedModel)
     pops = Dict{String,Int}(s=>0 for s = param("nature.targetspecies"))
@@ -74,7 +137,27 @@ function savepopulationdata(model::AgentBasedModel)
     end
     open(joinpath(param("core.outdir"), POPFILE), "a") do f
         for p in keys(pops)
-            println(f, "$(model.date);$(p);$(pops[p])")
+            println(f, join([model.date, p, pops[p]], ";"))
         end
     end
 end
+
+"""
+    saveindividualdata(model)
+
+Print a comma-separated set of lines to `individuals.csv`, listing all properties
+of all animal individuals in the model. May be called never, daily, monthly, yearly, or
+at the end of a simulation, depending on the parameter `nature.indoutfreq`.
+WARNING: Produces very big files!
+"""
+function saveindividualdata(model::AgentBasedModel)
+    data = ""
+    for a in allagents(model)
+        (typeof(a) != Animal) && continue
+        entry = join([model.date,a.id,a.pos[1],a.pos[2],a.species.name,a.sex,a.age,a.energy], ";")
+        data = data*entry*"\n"
+    end
+    open(joinpath(param("core.outdir"), INDFILE), "a") do f
+        print(f, data)
+    end
+end
diff --git a/src/core/simulation.jl b/src/core/simulation.jl
index 079052392983b15956866554462e517e42e590c0..36511a4e1e3f21ed0f6589ef75ef2a89b7466a6d 100644
--- a/src/core/simulation.jl
+++ b/src/core/simulation.jl
@@ -40,7 +40,7 @@ function stepsimulation!(model::AgentBasedModel)
         #The animal may have been killed, so we need a try/catch
         try stepagent!(getindex(model, a), model) catch keyerror end
     end
-    savepopulationdata(model)
+    recordnaturedata(model)
     model.date += Day(1)
 end
 
diff --git a/src/parameters.toml b/src/parameters.toml
index ea7411da90257fac7fdd3c31848f991d307afae8..d0032e310beaf2f3fd5182561e7f2a73f2140796 100644
--- a/src/parameters.toml
+++ b/src/parameters.toml
@@ -22,7 +22,9 @@ enddate = 2020-01-31
 
 [nature]
 targetspecies = ["Wolpertinger", "Wyvern"] # list of target species to simulate
-
+popoutfreq = "daily" # output frequency population-level data, daily/monthly/yearly/end/never
+indoutfreq = "end" # output frequency individual-level data, daily/monthly/yearly/end/never
+	
 [crop]
 cropmodel = "linear" # crop growth model to use, "linear" or "aquacrop" (not yet implemented)