When attempting to calculate a Nutrient Profile Model volumes and
masses need to be adjusted by the specific gravity of the product to
ensure a correct reading. This is managed directly by the
SGConverter
high level function that operates row-by-row on
a data.frame with a series of expected columns. This function is
composed of a series of different dispatcher function which will be
outlined below (see figure for hierarchy).
SGConverter logic
The central logic for specific gravity conversion is multiplying the
volume/weight of a product by a specific gravity multiplier. This is
distilled in he generic_specific_gravity
function.
generic_specific_gravity(100, 1.01)
#> [1] 101
In practise this package uses the SGtab
named vector
which contains a series of named products and their associated specific
gravity. This variable is not directly accessible to users but is used
by other specific gravity functions to determine the appropriate
specific gravity adjustments.
Breaking down SGConverter
SGConverter
works by taking the row of a data.frame as
it’s argument. It checks that row for a column called
product_type
. This column is used to define whether the
product is either a "food"
or "drink"
. The
value in this column is used by SGConverter
to determine
whether to pass the row to either an sg_food_converter
function or a sg_drink_converter
function.
Calculating specific gravity for food
If SGConverter
is passed a product_type
that is "food"
it passes the row to
sg_food_converter
. This function does a simple test of
whether the row contains a column called weight_g
, if this
column doesn’t contain NA
it returns the value contained in
weight_g
as we don’t have to adjust this for a specific
gravity. If the weight_g
column does contain
NA
the row is dispatches to the
sg_liquidfood_converter
function. The
sg_liquidfood_converter
checks for a food_type
column which contains a type value that should match a product included
in our SGtab
vector. The
sg_liquidfood_converter
checks if the
food_type
column is not an empty string, if this is true it
calculates the specific gravity adjusted volume by getting the value
from the volume_ml
column in the row and multiplying it by
the specific gravity value in the SGtab
which is indexed
out of the vector using the food_type
column value from the
passed row.
# example of how sg_food_converter behaves
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
data <- data.frame(weight_g = c(100, NA, NA),
volume_ml = c(NA, 10, 100),
product_type = c("food","food","food"),
food_type = c("","Ice cream", "Semi-skimmed milk"))
sg_food_converter(data[1, ])
#> [1] 100
# on all rows with dplyr
data %>%
rowwise() %>%
mutate(sg = sg_food_converter(pick(everything()))) %>%
select(sg)
#> # A tibble: 3 × 1
#> # Rowwise:
#> sg
#> <dbl>
#> 1 100
#> 2 13
#> 3 103
Calculating specific gravity for drinks
The logic for calculating specific gravity for drinks is slightly
more complicated. If the SGConverter
function found that
"drink"
was specified in the product_type
column it dispatches the row to the sg_drink_converter
function. This checks the row for a column called
drink_format
that specifies whether the drink is either:
ready to drink, a cordial or a powdered drink ("ready"
,
"cordial"
, "powdered"
). For each of these
options the sg_drink_converter
dispatches the row to an
additional function.
For "ready"
it dispatches to
sg_ready_drink_converter
. This function checks if the row
contains a drink_type
column and if that row contains an
empty string. If it does not contain an empty string it calls the
generic_specific_gravity
function passing the
volume_ml
column value and the specific gravity multiplier
indexed from SGtab
based on the value in
drink_type
. If drink_type
does contain an
empty string it just returns the volume_ml
column
value.
# example of how sg_ready_drink_converter behaves
data <- data.frame(volume_ml = c(30, 10, 100),
drink_type = c("Energy drink","Ice cream", "Semi-skimmed milk"))
sg_ready_drink_converter(data[1, ])
#> [1] 32.1
# on all rows with dplyr
data %>%
rowwise() %>%
mutate(sg = sg_ready_drink_converter(pick(everything()))) %>%
select(sg)
#> # A tibble: 3 × 1
#> # Rowwise:
#> sg
#> <dbl>
#> 1 32.1
#> 2 13
#> 3 103
For "cordial"
it dispatches to
sg_cord_drink_converter
. This function checks the row for a
nutrition_info
column to help determine how it should
proceed. This column contains information about how the drink has been
consumed, whether it has been directly consumed, following some
preparation instructions that dilute the cordial or if no instructions
were provided ("as consumed"
,
"preparation instructions given"
,
"preparation instructions not given"
). If
nutrition_info
is "as consumed"
the
volume_ml
column value and the specific gravity multipler
from the SGtab
for
"Cordial/squash ready to drink"
is passed to the
generic_specific_gravity
function. For
"preparation instructions given"
the expectation is that
there is an additional column in the row volume_water_ml
that specifies the volume of water used to dilute the cordial. If
"preparation instructions given"
is found in
nutrition_info
column it computes the sum of
volume_ml
and volume_water_ml
columns and
passes those to generic_specific_gravity
along with the
specific gravity multipler from the SGtab
for
"Cordial/squash ready to drink"
. For
"preparation instructions not given"
the
volume_ml
column value is passed to
generic_specific_gravity
function along with the specific
gravity multiplier indexed from SGtab
for
"Cordial/squash undiluted"
.
# example of how sg_cord_drink_converter behaves
data <- data.frame(volume_ml = c(30, 10, 100),
volume_water_ml = c(0, 90, NA),
nutrition_info = c("as consumed","preparation instructions given", "preparation instructions not given"))
sg_cord_drink_converter(data[1,])
#> [1] 30.9
data %>%
rowwise() %>%
mutate(sg = sg_cord_drink_converter(pick(everything()))) %>%
select(sg)
#> # A tibble: 3 × 1
#> # Rowwise:
#> sg
#> <dbl>
#> 1 30.9
#> 2 103
#> 3 109
If the drink_format
column value is
"powdered"
it dispatches to the
sg_powd_drink_converter
function. This again checks the row
for values in the nutrition_info
column to help determine
how to compute the specific gravity adjustment using the same 3
potential values defined above in the cordial
section. If
nutrition_info
is "as consumed"
the
volume_ml
column value and the specific gravity multipler
from the SGtab
for
"Cordial/squash ready to drink"
is passed to the
generic_specific_gravity
function. For
"preparation instructions given"
the expectation is that
there is an additional column in the row weight_g
that
specifies the weight in grams of the powdered drink used. If
"preparation instructions given"
is found in
nutrition_info
column it computes the sum of
weight_g
and volume_water_ml
columns and
passes those to generic_specific_gravity
along with the
specific gravity multipler from the SGtab
for
"Cordial/squash ready to drink"
. For
"preparation instructions not given"
the
weight_g
column value is returned unadjusted.
# example of how sg_powd_drink_converter behaves
data <- data.frame(volume_ml = c(90, NA, NA),
weight_g = c(NA, 10, 100),
volume_water_ml = c(0, 90, NA),
nutrition_info = c("as consumed","preparation instructions given", "preparation instructions not given"))
sg_powd_drink_converter(data[1,])
#> [1] 92.7
data %>%
rowwise() %>%
mutate(sg = sg_powd_drink_converter(pick(everything()))) %>%
select(sg)
#> # A tibble: 3 × 1
#> # Rowwise:
#> sg
#> <dbl>
#> 1 92.7
#> 2 103
#> 3 100
Summary
Overall most of the functions in SGConverter
are quite
constrictive. They expect particular column names to work and won’t just
work with any data. However, the building blocks to all these functions
is the generic_specific_gravity
which can be used to
develop your own functions for building a specific gravity conversion
pipeline. The existing steps shown here should serve as a template to
build your own specific gravity conversion pipeline.