Purpose

The objective of this workflow is to facilitate summarization, error detection, and daily monitoring of automatic feeding data from WinFire FireFeeder event logs.

Introduction

The FireFeeder automatic single-spaced feeding system is used to monitor individual feed intake and weight gain in swine and small ruminants. These systems are especially useful in selective breeding for growth and feed conversion. This work flow is ideal for reproducible protocols for data filtering and cleaning WinFire event logs. This may lead to novel behavioral signatures associated with dominance and aggression in growing-finisher swine. To make use of FireFeeders for behavior and performance measures, we provide a means to identify errors in feed intake prediction, weight measures, and quantify disturbances in data collection. The following work flow helps lab groups monitor, summarize, and process data accumulated from WinFire event logs.

Load Packages

library(tidyverse)
library(lubridate)
library(quantreg)
library(openxlsx)
library(gridExtra)
library(scales)

Inputs

Modify the working directory in the lines below. Indicate raw data file name and key file name.

paste("This report was created on ",today(), sep=" ")
## [1] "This report was created on  2019-01-24"
data_dir<-"C:/Users/Admin/OneDrive/Documents/phenomics/FIRE/test/"
log_dr <-data_dir 
setwd(data_dir)

raw <- read_csv("FIRE_1.23.2019.csv")
key <- read_csv("Key_4a.csv")

Raw Eventlog - Raw CSV File Containing WinFire Eventlog
Key File - Links Alternative animal ID to RFID Tag
A proper key file must contain columns named Tag and Ear_Tag [case sensitive]. The Tag column should contain unique 12-digit RFID numbers. the Ear_Tag column should contain alternative unique animal IDs (i.e. 1432a). The key file may include additional meta data columns belonging to animals (i.e. Sire, breed, lesion score, etc.)

Function Definition:

start_fire() - Prepare Event Log
filter_fire() - Filter Event Log
power_flag() - Flag Extreme Observations Based Robust Quantile Regression
swine_summary() - Summarize Swine Performance and Feeding Behavior
feeder_summary() - Summarize Location Performance and Error Accumulation
meta_fire() - Generate Grow-Finish Trial Summaries
spark_reprot() - Create Data Objects for Plotting and Optional Excel Workbook

setwd(data_dir)
source("QuickFire_source1.24.2019.R")

Example 1:

“I want to do a repeated measures analysis on pig growth and feeding behavior for 7 days of my trial. I have a proper key with unique animal IDs” -researcher

Step 1
Link key file to raw eventlog and select preferred date range. In this case, researcher only cares about 7 days.

series <- start_fire(raw, key, start="01-1-2019",end="01-7-2019")
series
## # A tibble: 2,977 x 17
##    Location Date       Entry  Exit   Consumed Weight Ear_Tag `Nursery Pen`
##       <dbl> <date>     <time> <time>    <dbl>  <dbl> <chr>   <chr>        
##  1        1 2019-01-01 04'24" 04'58"    0.036    0   000000~ <NA>         
##  2        1 2019-01-01 08'09" 08'20"    0        0   000000~ <NA>         
##  3        1 2019-01-01 20'42" 21'15"    0.015    0   000000~ <NA>         
##  4        1 2019-01-01 22'15" 22'35"    0.003    0   000000~ <NA>         
##  5        1 2019-01-01 30'38" 30'58"    0.022    0   000000~ <NA>         
##  6        1 2019-01-01 32'07" 32'18"   -0.001    0   000000~ <NA>         
##  7        1 2019-01-01 36'00" 36'07"   -0.008    0   000000~ <NA>         
##  8        1 2019-01-01 37'05" 37'12"   -0.05     0   000000~ <NA>         
##  9        1 2019-01-01 38'57" 44'34"    0.265   67.4 2339    ML3          
## 10        1 2019-01-01 45'08" 57'37"    0.246   61.8 1412    MR5          
## # ... with 2,967 more rows, and 9 more variables: `Front LS` <dbl>,
## #   `Middle LS` <dbl>, `Rear LS` <dbl>, zero.tag <dbl>,
## #   visit.length <dbl>, intake.rate <dbl>, barcode <int>, top.up <dbl>,
## #   trial.day <dbl>
colnames(series)
##  [1] "Location"     "Date"         "Entry"        "Exit"        
##  [5] "Consumed"     "Weight"       "Ear_Tag"      "Nursery Pen" 
##  [9] "Front LS"     "Middle LS"    "Rear LS"      "zero.tag"    
## [13] "visit.length" "intake.rate"  "barcode"      "top.up"      
## [17] "trial.day"

Step 2
Use the prepared file and remove all zero-tag events, remove events with 0kg or less intake, and 0kg or less platform weight. Note: there will be the same number of rows with less columns.

filtered <-filter_fire(series, zeros=F, min_intake=0, min_weight=0)
filtered
## # A tibble: 1,673 x 17
##    Location Date       Entry Exit  Consumed Weight Ear_Tag `Nursery Pen`
##       <dbl> <date>     <tim> <tim>    <dbl>  <dbl> <chr>   <chr>        
##  1        1 2019-01-01 00:38 00:44    0.265   67.4 2339    ML3          
##  2        1 2019-01-01 00:45 00:57    0.246   61.8 1412    MR5          
##  3        1 2019-01-01 00:58 00:59    0.064   69.6 1408    MR3          
##  4        1 2019-01-01 01:00 01:08    0.159   63.6 2337    ML4          
##  5        1 2019-01-01 03:01 03:06    0.2     69.4 1408    MR3          
##  6        1 2019-01-01 03:06 03:12    0.11    62   1412    MR5          
##  7        1 2019-01-01 03:12 03:23    0.254   67.6 2339    ML3          
##  8        1 2019-01-01 03:41 03:47    0.197   68   1127    MR5          
##  9        1 2019-01-01 03:47 03:50    0.113   68.4 2338    ML4          
## 10        1 2019-01-01 05:06 05:11    0.145   67.8 2339    ML3          
## # ... with 1,663 more rows, and 9 more variables: `Front LS` <dbl>,
## #   `Middle LS` <dbl>, `Rear LS` <dbl>, zero.tag <dbl>,
## #   visit.length <dbl>, intake.rate <dbl>, barcode <int>, top.up <dbl>,
## #   trial.day <dbl>

Step 3
Flag the data for suspicious feed intake and wight measures. Warning: only observation in the range of dates provided to the start_fire() function will be utilized. Results should be interpreted in this context.

flagged <-power_flag(filtered)
flagged
## # A tibble: 1,673 x 26
##    Location Date       Entry Exit  Consumed Weight Ear_Tag `Nursery Pen`
##       <dbl> <date>     <tim> <tim>    <dbl>  <dbl> <chr>   <chr>        
##  1        1 2019-01-01 00:38 00:44    0.265   67.4 2339    ML3          
##  2        1 2019-01-01 00:45 00:57    0.246   61.8 1412    MR5          
##  3        1 2019-01-01 00:58 00:59    0.064   69.6 1408    MR3          
##  4        1 2019-01-01 01:00 01:08    0.159   63.6 2337    ML4          
##  5        1 2019-01-01 03:01 03:06    0.2     69.4 1408    MR3          
##  6        1 2019-01-01 03:06 03:12    0.11    62   1412    MR5          
##  7        1 2019-01-01 03:12 03:23    0.254   67.6 2339    ML3          
##  8        1 2019-01-01 03:41 03:47    0.197   68   1127    MR5          
##  9        1 2019-01-01 03:47 03:50    0.113   68.4 2338    ML4          
## 10        1 2019-01-01 05:06 05:11    0.145   67.8 2339    ML3          
## # ... with 1,663 more rows, and 18 more variables: `Front LS` <dbl>,
## #   `Middle LS` <dbl>, `Rear LS` <dbl>, zero.tag <dbl>,
## #   visit.length <dbl>, intake.rate <dbl>, barcode <int>, top.up <dbl>,
## #   trial.day <dbl>, zero.weight <dbl>, low.weight <dbl>,
## #   high.weight <dbl>, slow.feed <dbl>, fast.feed <dbl>,
## #   pl.residual <dbl>, tr.residual <dbl>, weight.fl <dbl>, t.fl <dbl>

The flagged event file has all rows of the filtered tibble with new logical columns indicating if an event was flagged as a suspicious measurement in context of all event records for an individual.

colnames(flagged)
##  [1] "Location"     "Date"         "Entry"        "Exit"        
##  [5] "Consumed"     "Weight"       "Ear_Tag"      "Nursery Pen" 
##  [9] "Front LS"     "Middle LS"    "Rear LS"      "zero.tag"    
## [13] "visit.length" "intake.rate"  "barcode"      "top.up"      
## [17] "trial.day"    "zero.weight"  "low.weight"   "high.weight" 
## [21] "slow.feed"    "fast.feed"    "pl.residual"  "tr.residual" 
## [25] "weight.fl"    "t.fl"

Step 4
Finally, get a repeated measures summary for each individual by day over the period. notice the number of rows equal is the number of animals (16) x the number of days included in the start_fire() function (7).

example1.for.rm <- swine_summary(flagged, byday=T)
example1.for.rm
## # A tibble: 112 x 25
## # Groups:   Location, Ear_Tag [16]
##    Location Ear_Tag Date       delta.weight med.weight min.weight
##       <dbl> <chr>   <date>            <dbl>      <dbl>      <dbl>
##  1        1 1127    2019-01-01          3.2       68.6       67.4
##  2        1 1127    2019-01-02          3.2       70.1       69  
##  3        1 1127    2019-01-03          3.2       70.8       66.4
##  4        1 1127    2019-01-04          3.2       72.2       70.8
##  5        1 1127    2019-01-05          3.2       73.4       72.4
##  6        1 1127    2019-01-06          3.2       74         73.2
##  7        1 1127    2019-01-07          3.2       73.8       67.2
##  8        1 1408    2019-01-01          3.2       69         67.4
##  9        1 1408    2019-01-02          3.2       70.7       69.6
## 10        1 1408    2019-01-03          3.2       71.8       70  
## # ... with 102 more rows, and 19 more variables: max.weight <dbl>,
## #   mad.weight <dbl>, total.feed <dbl>, n.visits <int>,
## #   total.occupancy <dbl>, mean.visit <dbl>, mean.rate <dbl>,
## #   mean.rate.weighted <dbl>, mad.rate <dbl>, min.visit <dbl>,
## #   max.visit <dbl>, n.zero.weight <dbl>, n.t.flag <dbl>, n.p.flag <dbl>,
## #   n.low.weight <dbl>, n.high.weight <dbl>, n.slow.feed <dbl>,
## #   n.fast.feed <dbl>, gain.to.feed <dbl>
colnames(example1.for.rm)
##  [1] "Location"           "Ear_Tag"            "Date"              
##  [4] "delta.weight"       "med.weight"         "min.weight"        
##  [7] "max.weight"         "mad.weight"         "total.feed"        
## [10] "n.visits"           "total.occupancy"    "mean.visit"        
## [13] "mean.rate"          "mean.rate.weighted" "mad.rate"          
## [16] "min.visit"          "max.visit"          "n.zero.weight"     
## [19] "n.t.flag"           "n.p.flag"           "n.low.weight"      
## [22] "n.high.weight"      "n.slow.feed"        "n.fast.feed"       
## [25] "gain.to.feed"

Leap Instead of Steps I can skip intermediate tables by passing the output from one function to another using the pipe operator ‘%>%’. I get the same result.

example1.for.rm <- start_fire(raw, key, start="01-1-2019",end="01-7-2019") %>% filter_fire(zeros=F, min_intake=0, min_weight=0) %>% power_flag() %>% swine_summary(byday=T)

example1.for.rm
## # A tibble: 112 x 25
## # Groups:   Location, Ear_Tag [16]
##    Location Ear_Tag Date       delta.weight med.weight min.weight
##       <dbl> <chr>   <date>            <dbl>      <dbl>      <dbl>
##  1        1 1127    2019-01-01          3.2       68.6       67.4
##  2        1 1127    2019-01-02          3.2       70.1       69  
##  3        1 1127    2019-01-03          3.2       70.8       66.4
##  4        1 1127    2019-01-04          3.2       72.2       70.8
##  5        1 1127    2019-01-05          3.2       73.4       72.4
##  6        1 1127    2019-01-06          3.2       74         73.2
##  7        1 1127    2019-01-07          3.2       73.8       67.2
##  8        1 1408    2019-01-01          3.2       69         67.4
##  9        1 1408    2019-01-02          3.2       70.7       69.6
## 10        1 1408    2019-01-03          3.2       71.8       70  
## # ... with 102 more rows, and 19 more variables: max.weight <dbl>,
## #   mad.weight <dbl>, total.feed <dbl>, n.visits <int>,
## #   total.occupancy <dbl>, mean.visit <dbl>, mean.rate <dbl>,
## #   mean.rate.weighted <dbl>, mad.rate <dbl>, min.visit <dbl>,
## #   max.visit <dbl>, n.zero.weight <dbl>, n.t.flag <dbl>, n.p.flag <dbl>,
## #   n.low.weight <dbl>, n.high.weight <dbl>, n.slow.feed <dbl>,
## #   n.fast.feed <dbl>, gain.to.feed <dbl>
colnames(example1.for.rm)
##  [1] "Location"           "Ear_Tag"            "Date"              
##  [4] "delta.weight"       "med.weight"         "min.weight"        
##  [7] "max.weight"         "mad.weight"         "total.feed"        
## [10] "n.visits"           "total.occupancy"    "mean.visit"        
## [13] "mean.rate"          "mean.rate.weighted" "mad.rate"          
## [16] "min.visit"          "max.visit"          "n.zero.weight"     
## [19] "n.t.flag"           "n.p.flag"           "n.low.weight"      
## [22] "n.high.weight"      "n.slow.feed"        "n.fast.feed"       
## [25] "gain.to.feed"

Example 2:

“I want to understand individual variation in night feeding behavior for all the days of the trial in my eventlog. I’m not interested in repeated measures.” -researcher

Step 1
Prepare, filter, and flag your eventlog. Keep in mind that by default, the start date in the start_fire() function is before computers were invented, and the default end date is the day your run the code. Because I am interested in animal visits with out a weight, I will not provide a minim_weight cut-off value.

flagged <- start_fire(raw, key) %>% filter_fire(zeros=F, min_intake=0) %>% power_flag()
flagged
## # A tibble: 10,159 x 26
##    Location Date       Entry Exit  Consumed Weight Ear_Tag `Nursery Pen`
##       <dbl> <date>     <tim> <tim>    <dbl>  <dbl> <chr>   <chr>        
##  1        1 2018-12-05 19:22 19:25    0.075   38.8 2337    ML4          
##  2        1 2018-12-05 19:25 19:31    0.109   39.4 1412    MR5          
##  3        1 2018-12-05 19:32 19:38    0.07    39.6 1416    MR3          
##  4        1 2018-12-05 19:41 19:53    0.174   38.2 2337    ML4          
##  5        1 2018-12-05 21:52 21:58    0.135   39.2 1412    MR5          
##  6        1 2018-12-05 22:09 22:16    0.112   38.6 2337    ML4          
##  7        1 2018-12-05 23:04 23:05    0.032   39.6 1412    MR5          
##  8        1 2018-12-05 23:06 23:11    0.066   39   1416    MR3          
##  9        1 2018-12-06 01:20 01:21    0.027   38.8 2337    ML4          
## 10        1 2018-12-06 02:42 02:48    0.081   39   2337    ML4          
## # ... with 10,149 more rows, and 18 more variables: `Front LS` <dbl>,
## #   `Middle LS` <dbl>, `Rear LS` <dbl>, zero.tag <dbl>,
## #   visit.length <dbl>, intake.rate <dbl>, barcode <int>, top.up <dbl>,
## #   trial.day <dbl>, zero.weight <dbl>, low.weight <dbl>,
## #   high.weight <dbl>, slow.feed <dbl>, fast.feed <dbl>,
## #   pl.residual <dbl>, tr.residual <dbl>, weight.fl <dbl>, t.fl <dbl>
colnames(flagged)
##  [1] "Location"     "Date"         "Entry"        "Exit"        
##  [5] "Consumed"     "Weight"       "Ear_Tag"      "Nursery Pen" 
##  [9] "Front LS"     "Middle LS"    "Rear LS"      "zero.tag"    
## [13] "visit.length" "intake.rate"  "barcode"      "top.up"      
## [17] "trial.day"    "zero.weight"  "low.weight"   "high.weight" 
## [21] "slow.feed"    "fast.feed"    "pl.residual"  "tr.residual" 
## [25] "weight.fl"    "t.fl"

Step 2
Now that my event log is flagged and filtered, I will ask the swine_summary() function to summarize animal performance over the whole and report each measures by light and dark feeding hours. Note, the number of rows in output equal to the number of unique animals in the trial because the ‘byday = F’.

example2.night.behavior <- swine_summary(flagged, byday=F, night.light= T, sun.rise = "07:00:00", sun.set = "20:00:00")
example2.night.behavior
## # A tibble: 16 x 20
## # Groups:   Location [2]
##    Location Ear_Tag delta.weight n.visits total.occupancy mean.visit
##       <dbl> <chr>          <dbl>    <int>           <dbl>      <dbl>
##  1        1 1127            48.7      327           3563.       654.
##  2        1 1408            51.6      323           2456.       456.
##  3        1 1412            36.2      481           3465.       432.
##  4        1 1416            47.8      299           4525.       908.
##  5        1 2337            44.6      456           4046.       532.
##  6        1 2338            43.9      382           2137.       336.
##  7        1 2339            51.3      499           4163.       501.
##  8        1 2342            41.3      413           3193.       464.
##  9        2 1376            48        466           4408.       568.
## 10        2 1378            47.5      563           2581.       275.
## 11        2 1379            47.9      613           3816.       373.
## 12        2 1403            43.4      521           3079.       355.
## 13        2 1410            49.8      485           5318.       658.
## 14        2 2336            46.4      495           2683.       325.
## 15        2 2340            42.2      487           4606.       567.
## 16        2 2341            51.9      549           3960.       433.
## # ... with 14 more variables: mean.rate <dbl>, mean.rate.weighted <dbl>,
## #   mad.rate <dbl>, min.visit <dbl>, max.visit <dbl>, n.zero.weight <dbl>,
## #   night.mean.visit <dbl>, night.mean.rate <dbl>,
## #   night.mean.rate.weighted <dbl>, night.mad.rate <dbl>,
## #   night.min.visit <dbl>, night.max.visit <dbl>,
## #   night.n.zero.weight <dbl>, night.n.t.flag <dbl>
colnames(example2.night.behavior)
##  [1] "Location"                 "Ear_Tag"                 
##  [3] "delta.weight"             "n.visits"                
##  [5] "total.occupancy"          "mean.visit"              
##  [7] "mean.rate"                "mean.rate.weighted"      
##  [9] "mad.rate"                 "min.visit"               
## [11] "max.visit"                "n.zero.weight"           
## [13] "night.mean.visit"         "night.mean.rate"         
## [15] "night.mean.rate.weighted" "night.mad.rate"          
## [17] "night.min.visit"          "night.max.visit"         
## [19] "night.n.zero.weight"      "night.n.t.flag"

Example 3:

“I want to see a summarizations of feeder performance while I was on vacation. I want to see if feeder are accumulating errors (including zero-tag events, high platform weight, etc.) I also want to see if these errors are growing while I was away.” -researcher

Step 1 to Results
Prepare and flag your eventlog. Because interests is in number of zero tag events and general error accumulation for each feeder,do not filter the eventlog using the filter_fire() function. Use the byday=T to get a repeated measures

example3.feeder <- start_fire(raw, key, start="12-20-2018",end="01-7-2019") %>% power_flag() %>% feeder_summary(byday=T)

Note: the number of rows in the output is equal to the number of days included in start_fire() time the number of unique feeder locations.

example3.feeder
## # A tibble: 38 x 19
## # Groups:   Location [2]
##    Location Date       n.unique.tags feed.consumed n.tagged.events
##       <dbl> <date>             <dbl>         <dbl>           <dbl>
##  1        1 2018-12-20             8          18.5              88
##  2        1 2018-12-21             8          18.9              90
##  3        1 2018-12-22             8          19.0              81
##  4        1 2018-12-23             8          18.3             105
##  5        1 2018-12-24             8          19.3              83
##  6        1 2018-12-25             8          20.6              83
##  7        1 2018-12-26             8          19.8              97
##  8        1 2018-12-27             8          20.0             109
##  9        1 2018-12-28             8          21.8             114
## 10        1 2018-12-29             8          20.7             118
## # ... with 28 more rows, and 14 more variables: n.zt.events <dbl>,
## #   n.events <int>, n.zero.weight <dbl>, n.topups <dbl>, min.weight <dbl>,
## #   max.weight <dbl>, mad.weight <dbl>, n.t.fl <dbl>, n.p.fl <dbl>,
## #   n.low.weight <dbl>, n.high.weight <dbl>, n.slow.feed <dbl>,
## #   n.fast.feed <dbl>, mean.weight <dbl>
colnames(example3.feeder)
##  [1] "Location"        "Date"            "n.unique.tags"  
##  [4] "feed.consumed"   "n.tagged.events" "n.zt.events"    
##  [7] "n.events"        "n.zero.weight"   "n.topups"       
## [10] "min.weight"      "max.weight"      "mad.weight"     
## [13] "n.t.fl"          "n.p.fl"          "n.low.weight"   
## [16] "n.high.weight"   "n.slow.feed"     "n.fast.feed"    
## [19] "mean.weight"

Example 4:

“I have a messy FireFeeder eventlog, several swine trials to analyze, and an abstract to finish. I need to quickly report the basics the trial dates, the number of days, the proportion of error accumulation etc.” -researcher

Step 1 to Results
Prepare and flag your eventlog. Because interests is in relative accumulation of errors, do not filter the eventlog using the filter_fire() function.

example4.meta <- start_fire(raw, key) %>% power_flag() %>% meta_fire()
example4.meta
##                       A          B         
## Location              1          2         
## Start Date            2018-12-05 2018-12-05
## End Date              2019-01-23 2019-01-23
## N Days                49 days    49 days   
## N Animals             8          8         
## N records             9266       6780      
## N tagged records      4491       5802      
## % Zero Tag Events     51.53      14.42     
## % Zero Weight records 54.03      17.08     
## % Weight Flags        0.27       0.07      
## % High Weight          58.33     100.00    
## % Low Weight          41.67       0.00     
## % Trough Flags        4.27       0.43      
## % Fast Intake         87.37      51.72     
## % Slow Intake         12.63      48.28