The ddpcr package makes extensive use of the S3 class
inheritance system in R. The plate object is implemented as an S3 object
of class ddpcr_plate
with the R list
as its
base type. S3 objects are used for two main purposes: to override the
behaviour of some base R functions when applied to plate objects, and to
allow plate objects to inherit characteristics of other plate
objects.
Every S3 object has a base type upon which it is built. The base type of a plate object is a list, as it allows for an easy way to bundle together the several different R objects describing a plate into one. All information required to analyze a plate is part of the plate object. Every plate object contains the following nine items:
dirty
flag is set to
true and the user is informed that the plate should be re-analyzed in
order for the new parameters to take effect.Except for the version and dirty flag, all other seven items in the
plate’s list have corresponding getter and setter functions for
convenience. For example, the name of a dataset can be retrieved with a
call to name(plate)
and can be modified with a call to
name(plate) <- "my data"
. The only information about a
plate object that is not inside the plate’s list is the plate type. The
plate’s type is already stored as the plate object’s class in order for
S3 to function, so including the plate type in the list would be
redundant.
Since the plate object is an S3 object, it can benefit from the use
of generic functions. There are three common generic functions that the
plate object implements: print()
, plot()
, and
subset()
. The print()
and plot()
generics are very often implemented for S3 classes, and they are
especially useful for plate objects. The print()
method
does not take any extra arguments and is used to print a summary of a
plate object in a visually appealing way to the console. It gives an
overview of the most important parameters of the plate such as its type
and name, as well as the current analysis state of a plate. The
plot()
method generates a scatterplot of every well in the
plate object and can be highly customizable using the many arguments it
implements. While the base plot()
method uses base R
graphics, the plot()
method for ddpcr_plate
objects uses the ‘ggplot2’ package. The subset()
generic is
overridden by a method that can be called to retain a subset of wells
from a larger plate. The subset()
method uses two
additional arguments, wells
and samples
, to
specify which wells to select.
Inheritance means that every plate type has a parent plate type from which it inherits all its features, but specific behaviour can be added or modified. In ddpcr, transitive inheritance is implemented, which means that features are inherited from all ancestors rather than only the most immediate one. Multiple inheritance is not supported, meaning that each plate object can only have one parent.
The notion of inheritance is an important part of the ddpcr package, as it allows ddPCR data from different assay types to share many properties. For example, PN/PP assays are first treated using the analysis steps common to all ddPCR experiments, and then gated with an assay-specific step, so PN/PP assays can be thought of as inheriting the analysis from general ddPCR assays. Furthermore, FAM-positive and HEX-positive assays (defined in Section 2.1) are both PN/PP assays that share many similarities, so they can be thought of as inheriting many properties of a PN/PP assay. Another benefit of the ddpcr inheritance is that it allows any user to easily extend the functionality of the package by adding custom ddPCR plate types.
The most basic plate type is ddpcr_plate
, and every
plate type inherits from it, either directly or by inheriting from other
plate types that are descendants of ddpcr_plate
. This is
useful because it means that all functionality that is common to all
ddPCR experiments is implemented once for ddpcr_plate
, and
every other plate type automatically inherits all the methods that
ddpcr_plate
has. When a new plate object is created (with
the new_plate()
function) without specifying a plate type,
the ddpcr_plate
type is assumed.
Calling the analyze()
function on any plate will result
in running the ddPCR data through a series of steps that are defined for
the given plate type, and the read droplets will be assigned to one of
several clusters associated with the given plate type. The exact set of
analysis steps and potential clusters are determined by the plate type.
Plates of type ddpcr_plate
have four possible droplet
clusters: “undefined” for any droplet that has not been assigned a
cluster yet, “failed” for droplets in failed wells, “outlier” for
outlier droplets, and “empty” for droplets without any template in them.
Plates of type ddpcr_plate
have 4 analysis steps:
“INITIALIZE”, “REMOVE_FAILURES”, “REMOVE_OUTLIERS”, and “REMOVE_EMPTY”.
This means that any plate that is created will perform these basic steps
of removing failed wells, outlier droplets, and empty droplets. Other
plate types inherit the same clusters and steps by default, and can
alter them to be more appropriate for each specific type. For example,
PN/PP plate types use the same clusters, as well as three more: “rain”,
“positive” (PP), and “negative” (PN). PN/PP plate types also use the
same list of steps as the base type, with the addition of steps at the
end to perform droplet gating.
The most important functionality of the ddpcr package is the
ability to programmatically analyze a plate’s droplet data. As described
above, a plate object contains a list of ordered steps that can be
accessed via the steps()
command. The value of each element
in the list refers to the name of the function that implements that
analysis step. For example, the last step in the analysis of a basic
plate of type ddpcr_plate
is “remove_empty”, which means
that the remove_empty()
function is automatically called
when the analysis pipeline reaches this step.
One of the several properties of a plate object that is stored in the
plate’s underlying list object is its status. The status of a plate is
simply an integer that indicates how many analysis steps have been
taken. When a plate is created, the first step of initialization runs
automatically, and the plate’s status is set to one. To analyze a plate,
the analyze()
or next_step()
functions are
used. The next_step()
runs the next step by examining the
plate’s list of steps and its current state to determine what function
to call. The analyze()
function can be used to run the full
analysis pipeline on a plate, as it simply calls
next_step()
repeatedly until the analysis is completed.
There are several commonalities among all functions that implement the analysis steps:
params
.step_begin()
and
step_end()
functions to allow the user to see what step is
currently being run and to calculate how long each step takes.