The objective of this workflow is to facilitate summarization, error detection, and daily monitoring of automatic feeding data from WinFire FireFeeder event logs.
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.
library(tidyverse)
library(lubridate)
library(quantreg)
library(openxlsx)
library(gridExtra)
library(scales)
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.)
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")
“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"
“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"
“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"
“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