Chapter 8 Means Separation and Data Presentation
The previous two units focused on the design and analysis of effects in multiple treatment files. Our focus was to determine whether treat ent effects explained more of the variation among individuals in a population than error (or residual) effects, which are based on unexplained differences among individuals.
In the first half of this unit, we will learn three common tools used for testing the differences between treatments. This is often the key purpose of a research trial. We know going in that some treatments will be different. But we don’t how they will rank, and whether the difference between them will be great enough to infer one is better than another.
In the second half, we will learn how to present treatment means in tables and plots. Proper data allows the reader to not only grasp results, but even incorporate some of your findings into their own work
8.1 Case Study
Our sample dataset is inspired by Salas, M.C.; Montero, J.L.; Diaz, J.G.; Berti, F.; Quintero, M.F.; Guzmán, M.; Orsini, F. Defining Optimal Strength of the Nutrient Solution for Soilless Cultivation of Saffron in the Mediterranean. Agronomy 2020, 10, 1311.
Saffron is a spice made from the anthers of the saffron flow. It has a nuanced, sweet, complex flavor, and is used in dishes from Cornish saffron rolls to Spanish paella to Iranian tahdig. It comes from the anthers of the field-grown saffron flower and must be hand picked, making it very expensive.
In this study, saffron was grown hydroponically in 15-L pots filled with perlite, with four nutrient concentrations as defined by electroconductivity (EC): low (EC 2.0), medium (EC 2.5), high (EC 3.0), and very high (EC 4.0).  Not to be confused with the psychadelic parties in Marin County, California, the LSD test is as simple as this:
- calculate the least significant difference
- any treatment differences that are equal to or greater than the least significant difference are – you guessed it – significant
The least significant difference is calculated as follows:
\[LSD_{df, \alpha} = t_{df, \alpha} \cdot SED\]
Where \(LSD_{df, \alpha}\) is the least significant difference, given the degrees of freedom associated with the error effect and the desired signifance.
Does this formula look vaguely familiar? Does it remind you of what you were doing back in Unit 4? Great, because this is the same formula we used to calculate the distance between confidence limits and the sample mean back when we learned t-tests. Back then, we saw how the confidence interval was used to test the probability our observed difference between treatments was different from zero. Recall if zero fell outside our confidence interval, we inferred the two treatments were different. Similarly, if the difference between two treatments is greater than the least significant difference, we infer the treatments are significantly different.
In R, we use a function, LSD.test(), which is part of the agricolae package, to calculate the LSD. First, however, lets run an analysis of variance on our t data. The experiment was a randomized complete block design, so our linear additive model is:
\[ Y_{ij} = \mu +B_i + T_j + BT_{ij}\]
Where \(Y_{ij}\) is the number of corms, \(\mu\) is the overall population mean for the trial, \(B_i\) is the block effect, \(T_j\) is the treatment effect, and \(BT_{ij}\) is the block effect.
Our analysis of variance result is below. The effect of fertilization rate is highly significant. And this brings us to an important rule for using the LSD test. We only use the LSD test to separate means if the treatment effect is significant in our analysis of variance. Doing otherwise can lead to errors, as we will discuss below.
$block = as.factor(saffron$block)
saffron= aov(corm_number ~ block + rate, saffron)
saffron_model summary(saffron_model)
## Df Sum Sq Mean Sq F value Pr(>F)
## block 3 85 28.4 2.465 0.129
## rate 3 4473 1490.9 129.333 1.02e-07 ***
## Residuals 9 104 11.5
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
8.3 LSD Output in R
Now that we know the effect of fertilization rate is highly significant, we want to know how the individual treatments rank, and whether they are significantly different from one another. The results of our LSD test are below.
library(agricolae)
= LSD.test(saffron_model, "rate")
lsd lsd
## $statistics
## MSerror Df Mean CV t.value LSD
## 11.52778 9 257.125 1.32047 2.262157 5.43101
##
## $parameters
## test p.ajusted name.t ntr alpha
## Fisher-LSD none rate 4 0.05
##
## $means
## corm_number std r LCL UCL Min Max Q25 Q50 Q75
## 1X 249.25 4.272002 4 245.4097 253.0903 246 255 246.00 248.0 251.25
## 2X 284.75 2.629956 4 280.9097 288.5903 281 287 284.00 285.5 286.25
## 3X 254.25 2.872281 4 250.4097 258.0903 251 258 253.25 254.0 255.00
## 4X 240.25 5.439056 4 236.4097 244.0903 233 246 238.25 241.0 243.00
##
## $comparison
## NULL
##
## $groups
## corm_number groups
## 2X 284.75 a
## 3X 254.25 b
## 1X 249.25 b
## 4X 240.25 c
##
## attr(,"class")
## [1] "group"
Lets unpack this piece by peace. The output from the LSD test is in a list of tables.
8.3.1 Statistics Table
Let’s start with the $statistics
table. This explains how our LSD was calculated:
MSerror is the error or residual mean square. It should match that value from the ANOVA table above. Recall that MSerror is an estimate the variance within treatments – that is, the variation among plots unexplained by our model. Therefore, its square root is the standard deviation of the observations within each treatment.
DF is the degrees of freedom, which is used to calculate our t.value
Mean is just that – the overall mean of the trial.
CV is the coefficient of variance. By dividing the standard deviation by the mean, and multiplying by 100, we arrive at this value. Recall from Unit 6 that the CV is a measure of the quality control of the trial: how consistent were our experimental units?
The t-value is based on the degrees of freedom and \(\alpha\), the desired p-value (often 0.05) to be used to to test significance.
LSD is the product of the t-value and the standard error of the difference, which can be derived from MSerror and the number of replicates.
This is a lot to absorb, I realize. The most important two statistics for you to understand from this table are the CV and LSD. The other numbers are intermediate values, although if you list the LSD in a report you should also report the degrees of freedom used to calculate the t-value.
8.3.2 Means Table
The $means
table explains the distribution of observations within a treatment level around their sample mean. Most of these concepts we discussed in Unit 4. The statistics are:
- corm_number: These are our sample means for each level of treatment
- std: the standard error of the sample mean. This is unique to the sample mean for each treatment.
- r: the number of replicates per treatment level
- LCL and UCL: the lower confidence limit and upper confidence limit for each mean. These are calculated just as we did in Unit 4.
The remainder of the statistics show the minimum and maximum values, and the quartiles.
8.3.3 Groups Table
Often, the $groups table is the most interesting, for it tests the differences among levels of a treatment. The treatment means among levels are ranked from greatest to least.
- corm_number: again, the sample mean for each level of treatment
- group: this groups treatments that are statistically equivalent. Any means followed by the same letter are considered equal. In the table above, the mean corm numbers associated with the 1X and 3X rates of fertilizer are considered equal. Any means not followed by the same letter are consisdered statistically different at the p-level chosen to test those differences. The 2X rate produced significantly greater corm numbers than the other fertilizer rates. The 4X rate produced statistically lesser corm numbers than the other fertilizer rates.
8.4 Comparisonwise versus Experimentwise Error
It is important LSD tests not be used indiscriminatly to separate means. The problem is that each time we test the difference between two means, we have a 5% chance of committing a type I error. This is the probability of committing a Type I error in that comparison. This is known as the comparisonwise error rate.
The probability of committing a type I error across all the comparisons is known as the experimentwise error rate. If we only conduct one comparison, our experimentwise error rate is equal to the comparisonwise error rate. But if we conduct two comparisons, our experimentwise error rate is equal to the probability that comparisonwise errors will not occur in both comparison.
We can demonstrate this with some simple calculations. Before we start our comparisons, there is a 100% chance we have not committed an experimentwise Type I error. We cold express this as:
\[ \text{Experimentwise Error} = 1 \]
If we once comparison, there is 5% probability of a comparisonwise Type I error – and a 95% chance the error will not occur. We can express this as
\[ \text{Experimentwise Error} = 1 - 0.95 = 0.05 \]
If we have two comparisons, there is a 95% probability a comparisonwise Type I error won’t occur in the first comparison – and a 95% probability it won’t occur in the second comparison. But the probability it doesn’t occur in both comparisons is 0.95 * 0.95:
\[ \text{Experimentwise Error} = 1 - 0.95 \times 0.95 = 1 - 0.9025 = 0.0975 \]
Now the Experimentwise error rate is 0.0975, or 9.75%.
What about three comparisons?
\[ \text{Experimentwise Error} = 1 - 0.95 \times 0.95 \times 0.95 = 1 - 0.8573 = 0.1427 \]
The Experimentwise error rate is now 0.1427, or 14.27%.
Finally, what if we had 10 comparisons?
\[ \text{Experimentwise Error} = 1 - 0.95^{10} = 1 - 0.5987 = 0.4013\] Our experimentwise error rate is now about 0.40, or 40%.
As the number of our comparisons increases, so does the probability of an experimentwise error. How can we avoid this? The first method, mentioned above, is to not use the LSD test unless the ANOVA shows a significant treatment effect. We call this approach the F-protected LSD test.
The second approach is to use a multiple range test that increases its minimum significant difference for comparing treatments as the number of treatments increases.
8.5 Tukey’s Honest Significant Difference
If we are going to be comparing many treatments, it is better to use a minimum significant difference, like Tukey’s Honest Significant Different (HSD) Test. Tukey’s HSD is calculated very similarly to the least significant difference:
\[ \text{Tukey's HSD} = Q_{\alpha, df, k} \text{ } \cdot SED \]
The difference in Tukey’s HSD is that we use Q, the “studentized range distribution” (you can just call it Q in this course) in place of the t-distribution. Q differs from t in that its value is determined not only by \(\alpha\), the desired probability of a Type I error, and \(df\), the degrees of freedom associated with the error mean square, but also \(k\), the number of treatments.
The plot below includes four “Q” distribution curves, associated with 3, 5, 6, and 10 treatments. Observe how the distributions shift to the right as the number of treatments increases. This means that the minimum difference for significance also increases with the number of treatments.
= list()
tukey_list for(k in c(3:10)){
<- seq(0, 8, by = 0.005) # Specify x-values for ptukey function
x_ptukey <- ptukey(x_ptukey, nmeans = k, df = 3*k) # Apply ptukey function
y_ptukey = data.frame(x_ptukey) %>%
tukey_df cbind(y_ptukey) %>%
mutate(change = y_ptukey - lag(y_ptukey)) %>%
mutate(midpoint = (x_ptukey + lag(x_ptukey))/2) %>%
mutate(no_trt = k) %>%
as.data.frame()
=k-2
i= tukey_df
tukey_list[[i]]
}
= bind_rows(tukey_list)
tukey_df_all
%>%
tukey_df_all filter(no_trt %in% c(3,5,8,10)) %>%
mutate(no_trt = as.factor(no_trt)) %>%
ggplot(aes(x=midpoint, y=change, group=no_trt)) +
geom_point(aes(color=no_trt))
## Warning: Removed 4 rows containing missing values (geom_point).
The Tukey test output below is very similar to the LSD test output. The “$statistics” section is identical to that of the LSD output, except it now reports the minimum significant difference instead of the least significant difference. In the “$parameters” section, the “Studentized Range” value (Q) is given in place of the t-value. The “$groups” section can be interpreted the same for the minimum significant difference as for the least significant difference.
= HSD.test(saffron_model, "rate", group=TRUE)
tukey tukey
## $statistics
## MSerror Df Mean CV MSD
## 11.52778 9 257.125 1.32047 7.494846
##
## $parameters
## test name.t ntr StudentizedRange alpha
## Tukey rate 4 4.41489 0.05
##
## $means
## corm_number std r Min Max Q25 Q50 Q75
## 1X 249.25 4.272002 4 246 255 246.00 248.0 251.25
## 2X 284.75 2.629956 4 281 287 284.00 285.5 286.25
## 3X 254.25 2.872281 4 251 258 253.25 254.0 255.00
## 4X 240.25 5.439056 4 233 246 238.25 241.0 243.00
##
## $comparison
## NULL
##
## $groups
## corm_number groups
## 2X 284.75 a
## 3X 254.25 b
## 1X 249.25 b
## 4X 240.25 c
##
## attr(,"class")
## [1] "group"
Unlike the LSD test, the Tukey test does not need to be “protected” by first examing whether the Analysis of Variance treatment effect is significant. That said, the Tukey test is unlikely to indicate a significant difference between treatments without the ANOVA treatment effect being significant as well.
8.6 Linear Contrast
The last type of means comparison we will learn in this lesson is the linear contrast. Unlike the LSD and Tukey tests, linear contrasts may be used to separate two groups of treatments. While a lot of math may be introduced to the curious, in a linear contrast the statistician defines two groups of treatments through the use of coefficients; R then calculates their means and standard errors, and compares them using a t-test. There are multiple ways we can use a linear contrast, and our saffron dataset is a great way to introduce them.
8.6.1 Coefficients
Recall how a t-test between two treatments works. We calculate \(t\) as:
\[ t = \frac{(\bar{x}_1-\bar{x}_2) - (\mu_1-\mu_2)}{SED}\] Where \(\bar{x}_1 -\bar{x}_2\) is the difference between sample means, \((\mu_1-\mu_2)\) is the hypothesized difference between treatments (usually zero), and SED is the standard error of the difference.
For most situations, we could simplify the above equation to:
\[ t = \frac{\bar{x}_1-\bar{x}_2}{SED}\]
What about if we are comparing more than two treatments? For our saffron example, what if we wanted to calculate the difference between the mean of the two lower rates (1X and 2X) and the mean of the two higher rates (3X and 4X)? Lets call this difference \(L\). We would calculate this difference as:
\[ L = \frac{\bar{x}_{1X} + \bar{x}_{2X}}{2} - \frac{\bar{x}_{3X} + \bar{x}_{4X}}{2} \]
All we are doing above is 1) calculating the means for the two groups and 2) subtracting the mean of the 3X and 4X rates from the mean of the 1X and 2X rates. Now let’s express this same formula a little differently. What we are doing in the above equation is multiplying each number by \(1/2\):
\[ L = \frac{1}{2}(\bar{x}_{1X} + \bar{x}_{2X}) - \frac{1}{2}(\bar{x}_{3X} + \bar{x}_{4X})\]
In addition (bear with me!), when we subtract the mean of treatments 3X and 4X, it is the equivalent of adding the negative value of their mean to the mean of treatments 1X and 2X:
\[ L = \frac{1}{2}(\bar{x}_{1X} + \bar{x}_{2X}) + (- \frac{1}{2}(\bar{x}_{3X} + \bar{x}_{4X}))\]
Finally, we can arrange the equation above as:
\[ L = \frac{1}{2}\bar{x}_{1X} + \frac{1}{2}\bar{x}_{2X} - \frac{1}{2}\bar{x}_{3X} - \frac{1}{2}\bar{x}_{4X} + \]
The reason for this tortuous algebra flashback is to show you where the contrast coefficients come from. Each of the \(\frac{1}{2}\)s in the equation above is a contrast coefficient.
Let’s demonstrate this with our saffron data. Our saffron treatment means are:
= saffron %>%
saffron_means group_by(rate) %>%
summarise(corm_number = mean(corm_number)) %>%
ungroup()
saffron_means
## # A tibble: 4 x 2
## rate corm_number
## <chr> <dbl>
## 1 1X 249.
## 2 2X 285.
## 3 3X 254.
## 4 4X 240.
Now let’s add in a column with our coefficients:
= saffron_means %>%
saffron_coefficients mutate(coefficient = c(1/2, 1/2, -1/2, -1/2))
saffron_coefficients
## # A tibble: 4 x 3
## rate corm_number coefficient
## <chr> <dbl> <dbl>
## 1 1X 249. 0.5
## 2 2X 285. 0.5
## 3 3X 254. -0.5
## 4 4X 240. -0.5
We see that R has converted these to decimals. We then create a new column, “mean_x_coefficient,” that is the product of the original mean and the coefficient. We see these products are approximately half the value of the original sample mean (some may be less than half because of rounding errors).
= saffron_coefficients %>%
saffron_products mutate(mean_x_coefficient = corm_number * coefficient)
saffron_products
## # A tibble: 4 x 4
## rate corm_number coefficient mean_x_coefficient
## <chr> <dbl> <dbl> <dbl>
## 1 1X 249. 0.5 125.
## 2 2X 285. 0.5 142.
## 3 3X 254. -0.5 -127.
## 4 4X 240. -0.5 -120.
Finally, we can sum the mean_x_coefficient column to get the total difference among treatments.
= sum(saffron_products$mean_x_coefficient)
total_difference
paste("total difference = ", total_difference, sep="")
## [1] "total difference = 19.75"
One critical rule about requirements is their sum must always equal zero. Otherwise you are not completely identifying two groups to compare. Using coefficients that do not sum to zero can also suggest you are weighting one group unfairly compared to another.
###Contrast Calculations
To calculate \(t\), of course, we must divide \(L\) by the standard error of the difference:
\[ t = \frac{L}{SED}\]
So how is the standard error of the difference calculated?
Well, we know that the error mean square from our ANOVA is equal to the mean variance within treatments. And we know that if we take the square root of a variance, we get the standard deviation. And if we divide the standard deviation by the number of observations in each sample mean, we get the standard error.
In a paired t-test, where we are simply calculating a standard error for one set of numbers, the differences between each pair, the standard error of the difference is calculated the same as the standard error:
\[ SED = SE = \frac{s}{\sqrt{n}} \]
Where \(s\) is the standard deviation and \(n\) is the number of observations (reps) per treatment. This formula is equal to that below, where we use the variance, \(s^2\), in place of the standard deviation.
\[ SED = \sqrt{\frac{s^2}{n}} \]
When we work with two-treatment trials where the treatments are not paired or blocked, we account for the variance and number replicates individually. For treatment levels “1” and 2", the standard errror of the difference becomes \[ SED = \sqrt{\frac{s_1^2}{n_1} + \frac{s_2^2}{n_2}} \]
In most trials, however, we assume that the variances and number of replications are equal among treatments. So we can also express the above trial as:
\[ SED = \sqrt\frac{2 \cdot s^2}{n}\]
Recall that in the analsis of variance for a multiple treatment trial, the error mean squares is the mean variance within treatments. So the equation above is equivalent to:
\[ SED = \sqrt\frac{2\cdot EMS}{n}\]
In a linear contrast, however, our standard error of the difference must be scaled according to the coefficients we use, since we are no longer comparing two treatments, but multiple. So our equation becomes:
\[ SED = c \cdot \sqrt{\frac{EMS}{n}} \]
Where \(c\) is the square root of the sum of the squared constants is the For our example above, this sum, \(c\), would be:
\[ c = s\sqrt{\sum (\frac{1}{2})^2 + (\frac{1}{2})^2 + (- \frac{1}{2})^2 + (- \frac{1}{2})^2} \]
Which is equal to:
\[ c = \sqrt{\frac{1}{4} + \frac{1}{4} +\frac{1}{4} +\frac{1}{4}} = \sqrt{1} = 1\]
We can now calculate the standard error of the difference for our contrast. First, lets go back to our analysis of variance.
= aov(corm_number ~ rate, saffron)
saffron_model summary(saffron_model)
## Df Sum Sq Mean Sq F value Pr(>F)
## rate 3 4473 1490.9 94.66 1.28e-08 ***
## Residuals 12 189 15.7
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
We can see from the ANOVA output thtat our error mean square is 15.75. We know from the design itself that we had 4 replicates
So our final standard error of the difference for our contrast is:
\[ SED = 1 \cdot \sqrt{\frac{15.75}{4}} = 1 \cdot \sqrt{3.9375} = 1.984 \]
The t-value for our test, would then be:
\[ t = \frac{L}{SED} = \frac{19.75}{1.984} = 9.954 \]
The probability of a t-value of 10, given the 12 degrees of freedom associated with the error sum of squares, would be:
pt(9.954,12, lower.tail = FALSE)
## [1] 1.882084e-07
8.6.2 Linear Contrasts with R
We can automate linear contrast testing, of course, using R and the glht() function of the multcomp package. First, we need to define a matrix (table) of contrast coefficients for R. To get the coefficients in the correct order, lets double check the order in which the rate levels are listed in R. It is important to make sure our treatment is classified as a factor. We can do this using the as.factor() function
$rate = as.factor(saffron$rate) saffron
We can then list the order of the levels in treatment rate:
levels(saffron$rate)
## [1] "1X" "2X" "3X" "4X"
We can see they follow the order 1X, 2X, 3X, 4X. We will therefore form a matrix, K, with the appropriate coefficients.
= matrix(c(1/2, 1/2, -1/2, -1/2),1)
K
K
## [,1] [,2] [,3] [,4]
## [1,] 0.5 0.5 -0.5 -0.5
We are now ready to run our contrast. First, we need to slightly alter our ANOVA model by adding a zero (0) between the tilde (~) and the treatment name. This is one of of those one-off ideosyncrasities of R.
library(multcomp)
## Loading required package: mvtnorm
## Loading required package: survival
## Loading required package: TH.data
## Loading required package: MASS
##
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
##
## select
##
## Attaching package: 'TH.data'
## The following object is masked from 'package:MASS':
##
## geyser
= aov(corm_number ~ 0 + rate, saffron)
saffron_model_for_contrast
= glht(saffron_model_for_contrast, linfct=K)
low_vs_high summary(low_vs_high)
##
## Simultaneous Tests for General Linear Hypotheses
##
## Fit: aov(formula = corm_number ~ 0 + rate, data = saffron)
##
## Linear Hypotheses:
## Estimate Std. Error t value Pr(>|t|)
## 1 == 0 19.750 1.984 9.953 3.77e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## (Adjusted p values reported -- single-step method)
And there we have it, our contrast in seconds. Now let’s interpret this. Estimate is the difference between groups. Since we subtracted the mean of the 3x and 4X rates from the mean of the 1X and 2X rates, the positive estimate value indicates the lower rates produced greater corm numbers than the higher rates.
“Std. Error” is the standard error of the difference, as we calculated above. The t-value is equal to the estimate divided by the standard error of the difference. Finally, “Pr(>|t|)” is the probability of observing the t-value by chance. In this case, it is \(3.77 \times 10^{-7}\), very close to zero. We conclude the lower two rates, as a group, produce greater corms than the upper two rates as a group.
We can quickly as other questions of our data. For example, do the middle two rates (2X and 3X) produce a greater corm number than the lowest (1X and highest (4X) rates? Again, let’s examine the order of our rate levels.
levels(saffron$rate)
## [1] "1X" "2X" "3X" "4X"
In order to subtract the mean corm number of rates 1X and 4X from the mean corm number of rates 2X and 3X, we will need to calculate the difference as:
\[ L = (-\frac{1}{2})\bar{x}_{1X} + (\frac{1}{2})\bar{x}_{2X} + (\frac{1}{2})\bar{x}_{3X} + (- \frac{1}{2})\bar{x}_{4X} + \]
So our contrasts coefficients would be \(-\frac{1}{2}\), \(\frac{1}{2}\), \(\frac{1}{2}\), \(-\frac{1}{2}\). Our contrast maxtrix is then:
= matrix(c(-1/2, 1/2, 1/2, -1/2),1)
K
K
## [,1] [,2] [,3] [,4]
## [1,] -0.5 0.5 0.5 -0.5
We are now ready to run our contrast. First, we need to slightly alter our ANOVA model by adding a zero (0) between the tilde (~) and the treatment name. This is one of of those one-off ideosyncrasities of R.
library(multcomp)
= aov(corm_number ~ 0 + rate, saffron)
saffron_model_for_contrast
= glht(saffron_model_for_contrast, linfct=K)
low_high_vs_middle summary(low_high_vs_middle)
##
## Simultaneous Tests for General Linear Hypotheses
##
## Fit: aov(formula = corm_number ~ 0 + rate, data = saffron)
##
## Linear Hypotheses:
## Estimate Std. Error t value Pr(>|t|)
## 1 == 0 24.750 1.984 12.47 3.14e-08 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## (Adjusted p values reported -- single-step method)
We see this estimated difference is even greater than the previous contrast. The significant t-value suggests there may be a parabolic (curved) response of corm number to nutrient solution rate. Indeed, if we plot the mean, we can see a parabolic response
%>%
saffron_means ggplot(aes(x=rate, y=corm_number)) +
geom_point(size = 3, color="blue")
8.7 Means Presentation
Something I have always overlooked in teaching this course is also something that is most basic: once we have separated our means, how do we present them to others? There are two ways to present means: in a table, or in a plot. Both have advantages and disadvantages. In a table, you provide raw statistics (treatment means, standard errors, and perhaps, means groupings from an LSD or Tukey test). A reader can use these values to recalculate your statistics, say, if they wanted to separate the means at p=0.10, p=0.1, or p=0.001.
A figure, one the otherhand, allows the reader to quickly grasp treatment differences, or patterns among treatments. In addition, they are colorful and – lets face it – more inviting than a dense table of numbers.
Whichever format we use, however, we need to present our treatment means and some reference for the reader to gauge whether those means are statistically equal or different.
8.7.1 Means Tables
Tables can be tricky, especially when many types of measures are included. If we are reporting results from a factorial experiment, we may be tempted to list the levels of one factor down rows, and the other factor across columns. I would generally discourage this, however, unless required to fit the table neatly into a publication space. Generally, the long form of means presentation is best.
In the long form of data, cases (individual observations or treatment means) are listed down the rows. Measurements from each cases are listed across rows.
For our saffron data, our table would start like this:
= saffron_means
final_table
::kable(final_table) knitr
| rate | corm_number |
|---|---|
| 1X | 249.25 |
| 2X | 284.75 |
| 3X | 254.25 |
| 4X | 240.25 |
To this table, we may wish to add the standard error of the difference. Remember, the standard error of the difference for an LSD or Tukey test is equal to:
\[ SED = \sqrt{\frac{(2 \cdot EMS)}{n}} \]
So for our saffron trial, where the error mean square is 15.75 (we can get this from either the ANOVA table or the LSD output)and the number of replications is 4, the standard error of the difference is:
\[ SED = \sqrt{\frac{(2 \cdot 15.75)}{4}} = \sqrt{7.875} = 2.80 \]
We can add this below the means
= saffron_means %>%
final_table mutate(rate = as.character(rate)) %>%
rbind(c("SED", 2.80))
::kable(final_table) knitr
| rate | corm_number |
|---|---|
| 1X | 249.25 |
| 2X | 284.75 |
| 3X | 254.25 |
| 4X | 240.25 |
| SED | 2.8 |
It is also important to indicate the number of replications of the treatments. We can add another row to the table with N.
= saffron_means %>%
final_table mutate(rate = as.character(rate)) %>%
rbind(c("SED", 2.80)) %>%
rbind(c("N", 4))
::kable(final_table) knitr
| rate | corm_number |
|---|---|
| 1X | 249.25 |
| 2X | 284.75 |
| 3X | 254.25 |
| 4X | 240.25 |
| SED | 2.8 |
| N | 4 |
We should add the LSD, to make it easy for readers to compare treatments. We will want also, to include the \(\alpha\) which was used to calcualate the LSD. That way, the reader will know whether the risk of a Type I error – that the LSD will separate treatments that are not truely different – is 5% or some other probability.
= saffron_means %>%
final_table mutate(rate = as.character(rate)) %>%
rbind(c("SED", 2.80)) %>%
rbind(c("N", 4)) %>%
rbind(c("LSD (0.05)", 6.11))
::kable(final_table) knitr
| rate | corm_number |
|---|---|
| 1X | 249.25 |
| 2X | 284.75 |
| 3X | 254.25 |
| 4X | 240.25 |
| SED | 2.8 |
| N | 4 |
| LSD (0.05) | 6.11 |
We might want to include the p-value from the ANOVA table, so the reader knows that the LSD is protected. The p-value for the saffron trial is \(1.28 \times 10^{-8}\). This is an extremely small number. It is acceptable for us to simplify this in the table, indicating that the probability of F was less than 0.001.
= saffron_means %>%
final_table mutate(rate = as.character(rate)) %>%
rbind(c("SED", 2.80)) %>%
rbind(c("N", 4)) %>%
rbind(c("LSD (0.05)", 6.11)) %>%
rbind(c("Pr<F", "<0.001"))
::kable(final_table) knitr
| rate | corm_number |
|---|---|
| 1X | 249.25 |
| 2X | 284.75 |
| 3X | 254.25 |
| 4X | 240.25 |
| SED | 2.8 |
| N | 4 |
| LSD (0.05) | 6.11 |
| Pr<F | <0.001 |
Finally, if imperial (pounds, acres, etc) or metric units were used to measure the response variable, it is important to indicate that in the table. Indicating those in parentheses after the variable name is appropriate.
8.7.2 Plotting Means
When we work with categorical treatments (that is, treatments levels that are defined by words), or even numerical variables treated as categorical variables (as in the saffron example) we should use a bar plot, not a line plot, to visualize the data. An easy way to determine whether a bar plot should be used is this: if you are using an LSD or Tukey’s test to separate your means, you should use a bar plot. A line plot, which suggests treatment levels are numerically related to (higher or lower than) each other should be used to fit regression models, where the analysis defines a continuous relationship between Y and X.
A basic bar plot will has a bar representing each treatment mean. The treatment level is indicated along the x (horizontal) axis. The sample mean is indicated along the y (vertical) axis. The bar height indicates the sample mean.
%>%
saffron_means ggplot(aes(x=rate, y=corm_number)) +
geom_bar(stat="identity", color="black", fill = "darkolivegreen") +
theme(axis.text = element_text(size=12),
axis.title = element_text(size=16))
This plot, however, does not provide the viewer any sense of whether corm number is significantly different among treatments. For that purpose, we can add error bars.
%>%
saffron_means ggplot(aes(x=rate, y=corm_number)) +
geom_bar(stat="identity", color="black", fill = "darkolivegreen") +
geom_errorbar(aes(ymin=corm_number-1.81, ymax=corm_number+1.81), width=0.4, size=1) +
theme(axis.text = element_text(size=12),
axis.title = element_text(size=16))
These error bars stretch from one standard error of the mean below the sample mean to one standard error of the mean above the sample mean. As a general rule of thumb, the least significant difference is approximately 2 standard errors of the mean. If the error bars from two treatment levels don’t overlap, then the two treatments are likely different. We can see in the plot that the corm number for the 1X and 3X fertilizer rates are very similar.
Alternatively, we could set the error bar height equal to the least significant difference itself.
%>%
saffron_means ggplot(aes(x=rate, y=corm_number)) +
geom_bar(stat="identity", color="black", fill = "darkolivegreen") +
geom_errorbar(aes(ymin=corm_number-5.63, ymax=corm_number+5.63), width=0.4, size=1) +
theme(axis.text = element_text(size=12),
axis.title = element_text(size=16))
This allows the viewer to use the actual LSD to separate treatment means. If the range of the error bar from one treatment does not include the mean of another treatment, then the two treatments are not equal.
Significant differences among treatments can also be indicated by including letter groupings from an LSD or Tukey’s test.
$groups %>%
lsdrownames_to_column(var="rate") %>%
ggplot(aes(x=rate, y=corm_number)) +
geom_bar(stat="identity", color="black", fill = "darkolivegreen") +
geom_text(aes(x=rate, y=corm_number+15, label=groups), size=6) +
theme(axis.text = element_text(size=12),
axis.title = element_text(size=16))
We will learn more about graphics in the exercises this unit. A very comprehensive resource for creating plots in R is R Graphics Cookbook, by Winston Chang. An online copy is available at https://r-graphics.org/. Print copies can also be purchased from common booksellers. This book explains not only how to create plots, but how to adjust labels, legends, axis lables, and so on.
8.8 Exercise: LSD and Tukey’s HSD
In the lecture, we learned two ways of grouping treatment means: by Least Significant Difference" and by Tukey’s HSD (Honest Significant Difference. Both of these tests can be run very quicly in R.
8.8.1 Case Study: Common Bean
The yield of organically-grown common bean was assessed following four crops: conventional broccoli, green_manure, fallow, and organic broccoli. Yield is in tons per hectare. We wish to separate the treatment means.
= read.csv("data-unit-8/exercise_data/common_bean.csv")
common_bean head(common_bean)
## block prev_crop fresh_weight
## 1 1 conv_broccoli 52.15924
## 2 1 green_manure 49.82191
## 3 1 organic_broccoli 36.80926
## 4 1 fallow 54.05342
## 5 2 conv_broccoli 52.79726
## 6 2 fallow 47.68364
First, we need to create our linear model and run our analysis of variance. The trial is a randomized complete block design, so our linear model is:
fresh_weight = mu + block + prev_crop + error
Our model statement in R is:
= aov(fresh_weight ~ block + prev_crop, data=common_bean) common_bean_model
Our analysis of variance is:
summary(common_bean_model)
## Df Sum Sq Mean Sq F value Pr(>F)
## block 1 2.4 2.37 0.179 0.6802
## prev_crop 3 428.9 142.98 10.835 0.0013 **
## Residuals 11 145.1 13.20
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
We see the effect of previous crop is significant, so now we want to separate the treatment means.
8.8.2 Least Significant Difference
Both the least significant test and Tukey’s Honest Significant Test will be run using functions from the agricolae package. We load the agricolae package to our session using the library() command
library(agricolae)
After this. We can run the least significant test using tne LSD.test() function. This argument takes two arguments: a linear model, and the treatment we wish to separate (which must be given in quotes). We can simply reference the common_bean_model created above.
= LSD.test(common_bean_model, "prev_crop")
lsd_common_bean lsd_common_bean
## $statistics
## MSerror Df Mean CV t.value LSD
## 13.19522 11 48.04057 7.561363 2.200985 5.653408
##
## $parameters
## test p.ajusted name.t ntr alpha
## Fisher-LSD none prev_crop 4 0.05
##
## $means
## fresh_weight std r LCL UCL Min Max
## conv_broccoli 52.79032 1.242884 4 48.79276 56.78788 51.68091 54.52386
## fallow 50.53382 2.718281 4 46.53625 54.53138 47.68364 54.05342
## green_manure 49.52910 5.135442 4 45.53153 53.52666 44.06679 56.34669
## organic_broccoli 39.30905 3.723475 4 35.31149 43.30662 36.10887 44.26354
## Q25 Q50 Q75
## conv_broccoli 52.03966 52.47825 53.22891
## fallow 48.93562 50.19911 51.79730
## green_manure 46.92744 48.85145 51.45311
## organic_broccoli 36.63416 38.43190 41.10679
##
## $comparison
## NULL
##
## $groups
## fresh_weight groups
## conv_broccoli 52.79032 a
## fallow 50.53382 a
## green_manure 49.52910 a
## organic_broccoli 39.30905 b
##
## attr(,"class")
## [1] "group"
The test results are returned in a list, which in R is a collection of objects grouped under one name (“lsd_common_bean” in our code above). We can reference different parts of this list the same way you would reference columns within a data frame: using the dollar sign to separate the list name and the object. For example, if we just wanted to look at our $statistics, we would type:
$statistics lsd_common_bean
## MSerror Df Mean CV t.value LSD
## 13.19522 11 48.04057 7.561363 2.200985 5.653408
We can reference the means groupings, as well:
$groups lsd_common_bean
## fresh_weight groups
## conv_broccoli 52.79032 a
## fallow 50.53382 a
## green_manure 49.52910 a
## organic_broccoli 39.30905 b
This selectivity is handy if we are writing a report and just want to reference part of the output.
In the grouping above, we see that common bean fresh weight was statistically similar following conventional broccoli, fallow, and green manure. Common bean fresh weight following organic broccoli, however, was statistically less than following the other three treatments.
8.8.3 Tukey HSD
We can separate the means, very similarly, using Tukey’s Honest Significant Difference. We use the HSD.test() function. The first two arguments are the same as for the LSD.test(): the linear model and treatment (in quotes) we wish to separate. We add a third argument, “group = TRUE.” This tells the HSD.test to return a letter-grouping of treatment means.
= HSD.test(common_bean_model, "prev_crop", group = TRUE)
common_bean_hsd common_bean_hsd
## $statistics
## MSerror Df Mean CV MSD
## 13.19522 11 48.04057 7.561363 7.730267
##
## $parameters
## test name.t ntr StudentizedRange alpha
## Tukey prev_crop 4 4.256143 0.05
##
## $means
## fresh_weight std r Min Max Q25 Q50
## conv_broccoli 52.79032 1.242884 4 51.68091 54.52386 52.03966 52.47825
## fallow 50.53382 2.718281 4 47.68364 54.05342 48.93562 50.19911
## green_manure 49.52910 5.135442 4 44.06679 56.34669 46.92744 48.85145
## organic_broccoli 39.30905 3.723475 4 36.10887 44.26354 36.63416 38.43190
## Q75
## conv_broccoli 53.22891
## fallow 51.79730
## green_manure 51.45311
## organic_broccoli 41.10679
##
## $comparison
## NULL
##
## $groups
## fresh_weight groups
## conv_broccoli 52.79032 a
## fallow 50.53382 a
## green_manure 49.52910 a
## organic_broccoli 39.30905 b
##
## attr(,"class")
## [1] "group"
The HSD.test output, like the lsd output is a list. We can again reference individual tables within it:
$groups common_bean_hsd
## fresh_weight groups
## conv_broccoli 52.79032 a
## fallow 50.53382 a
## green_manure 49.52910 a
## organic_broccoli 39.30905 b
8.8.4 Practice: Apple
Apple yield was measured after treatment with four biostimulants. The design was a randomized complete block. The yield was measured in tons per hectare.
= read.csv("data-unit-8/exercise_data/apple.csv") apple
<IP_ADDRESS> Analysis of Variance
Run the analysis of variance. Your results should be:
Df Sum Sq Mean Sq F value Pr(>F)
block 1 0.40 0.40 0.076 0.78800
foliar_trt 3 162.10 54.03 10.345 0.00156 **
Residuals 11 57.45 5.22
Signif. codes: 0 ‘’ 0.001 ‘’ 0.01 ‘’ 0.05 ‘.’ 0.1 ‘ ’ 1
<IP_ADDRESS> Least Significant Difference
Run the Least Significant Difference test on the treatment means. Your results should look like:
$statistics MSerror Df Mean CV t.value LSD 5.222995 11 47.96522 4.764676 2.200985 3.55682
$parameters test p.ajusted name.t ntr alpha Fisher-LSD none foliar_trt 4 0.05
$means yield std r LCL UCL Min Max Q25 Q50 Q75 conv 45.70984 1.678378 4 43.19478 48.22489 44.44665 48.15233 44.71817 45.12018 46.11185 conv+calcium 44.08293 1.274706 4 41.56788 46.59798 42.34804 45.30697 43.58313 44.33835 44.83815 conv+seaweed 51.90854 2.073312 4 49.39349 54.42359 50.18903 54.78184 50.51099 51.33165 52.72920 conv+si+zn 50.15958 3.246956 4 47.64452 52.67463 46.88179 54.60709 48.55789 49.57471 51.17639
$comparison NULL
$groups yield groups conv+seaweed 51.90854 a conv+si+zn 50.15958 a conv 45.70984 b conv+calcium 44.08293 b
attr(,“class”) [1] “group”
<IP_ADDRESS> Tukey’s HSD
Run the Honest Significant Difference test on the treatment means. Your results should look like:
$statistics MSerror Df Mean CV MSD 5.222995 11 47.96522 4.764676 4.863468
$parameters test name.t ntr StudentizedRange alpha Tukey foliar_trt 4 4.256143 0.05
$means yield std r Min Max Q25 Q50 Q75 conv 45.70984 1.678378 4 44.44665 48.15233 44.71817 45.12018 46.11185 conv+calcium 44.08293 1.274706 4 42.34804 45.30697 43.58313 44.33835 44.83815 conv+seaweed 51.90854 2.073312 4 50.18903 54.78184 50.51099 51.33165 52.72920 conv+si+zn 50.15958 3.246956 4 46.88179 54.60709 48.55789 49.57471 51.17639
$comparison NULL
$groups yield groups conv+seaweed 51.90854 a conv+si+zn 50.15958 ab conv 45.70984 bc conv+calcium 44.08293 c
attr(,“class”) [1] “group”
8.8.5 Practice: Wheat Treatment with Mildew
Four foliar treatments against powdery mildew in wheat were tested in Rothemsted, England, for their effect on grain yield. The same fungicide was tested with four timings: 0 (none), 1 (early), 2 (late), R (repeated).
The treatment was a randomized complete block design. Yield was measured in tons per hectare.
= read.csv("data-unit-8/exercise_data/mildew.csv")
wheat_mildew head(wheat_mildew)
## plot trt block yield
## 1 1 T2 B1 5.73
## 2 2 R B1 6.08
## 3 3 T0 B1 5.26
## 4 4 T1 B1 5.89
## 5 5 T0 B2 5.37
## 6 6 T2 B2 5.95
<IP_ADDRESS> Analysis of Variance
Run the analysis of variance on the response of wheat yield to fungicide treatment. Your results should look like:
Df Sum Sq Mean Sq F value Pr(>F)
block 8 5.085 0.6356 17.53 2.79e-08 trt 3 3.129 1.0432 28.77 4.05e-08 Residuals 24 0.870 0.0363
Signif. codes: 0 ‘’ 0.001 ‘’ 0.01 ‘’ 0.05 ‘.’ 0.1 ‘ ’ 1
<IP_ADDRESS> Least Significant Difference
Run the Least Significant Difference test on the treatment means. Your results should look like:
$statistics MSerror Df Mean CV t.value LSD 0.03625532 24 5.801944 3.281802 2.063899 0.1852542
$parameters test p.ajusted name.t ntr alpha Fisher-LSD none trt 4 0.05
$means yield std r LCL UCL Min Max Q25 Q50 Q75 R 5.942222 0.4649403 9 5.811228 6.073217 5.06 6.54 5.76 6.01 6.18 T0 5.310000 0.4519956 9 5.179006 5.440994 4.38 5.82 5.16 5.26 5.73 T1 5.867778 0.4602928 9 5.736783 5.998772 5.04 6.45 5.59 5.89 6.26 T2 6.087778 0.3347304 9 5.956783 6.218772 5.63 6.48 5.76 6.14 6.43
$comparison NULL
$groups yield groups T2 6.087778 a R 5.942222 ab T1 5.867778 b T0 5.310000 c
attr(,“class”) [1] “group”
<IP_ADDRESS> Tukey’s Honest Significant Difference Test
Run the HSD test on the treatment means. Your results should look like:
$statistics MSerror Df Mean CV MSD 0.03625532 24 5.801944 3.281802 0.2476109
$parameters test name.t ntr StudentizedRange alpha Tukey trt 4 3.901262 0.05
$means yield std r Min Max Q25 Q50 Q75 R 5.942222 0.4649403 9 5.06 6.54 5.76 6.01 6.18 T0 5.310000 0.4519956 9 4.38 5.82 5.16 5.26 5.73 T1 5.867778 0.4602928 9 5.04 6.45 5.59 5.89 6.26 T2 6.087778 0.3347304 9 5.63 6.48 5.76 6.14 6.43
$comparison NULL
$groups yield groups T2 6.087778 a R 5.942222 a T1 5.867778 a T0 5.310000 b
8.9 Exercise: Linear Contrasts
Linear contrasts are different from least significant difference (LSD) or Tukey’s honest significant difference (HSD) tests in that they can test groups of treatments. As we learned in the lecture, the simplest way to think of a linear constrast is as a t-test between two groups.
We also learned in the lecture that we use contrast coefficients to define these groups. The coefficients for one group should add up to 1; the coefficients of the other group should add up to -1. That way, we subtract the average value for treatments in one group from the average value for treatments in the other group.
8.9.1 Case Study: Winter Canola Cultivar Trial.
Six winter canola cultivars were tested in a randomized complete block trial in Manitoba. Yield is in kg / ha.
= read.csv("data-unit-8/exercise_data/canola_gd.csv")
canola head(canola)
## block cultivar yield
## 1 1 Bob 1508.424
## 2 2 Bob 1962.072
## 3 3 Bob 1364.861
## 4 4 Bob 1832.715
## 5 1 Donna 1212.190
## 6 2 Donna 1629.680
<IP_ADDRESS> ANOVA
First, let’s run our analysis of variance
= aov(yield ~ block + cultivar, data = canola)
canola_model summary(canola_model)
## Df Sum Sq Mean Sq F value Pr(>F)
## block 1 18600 18600 0.171 0.684822
## cultivar 5 4551035 910207 8.344 0.000388 ***
## Residuals 17 1854462 109086
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
<IP_ADDRESS> Least Significant Difference Test
Next, lets run an LSD test.
library(agricolae)
= LSD.test(canola_model, "cultivar")
lsd_canola lsd_canola
## $statistics
## MSerror Df Mean CV t.value LSD
## 109086 17 2296.983 14.37894 2.109816 492.7357
##
## $parameters
## test p.ajusted name.t ntr alpha
## Fisher-LSD none cultivar 6 0.05
##
## $means
## yield std r LCL UCL Min Max Q25
## Bill 2296.268 326.6421 4 1947.851 2644.685 1865.903 2614.081 2142.888
## Bob 1667.018 277.4666 4 1318.601 2015.435 1364.861 1962.072 1472.533
## Donna 1833.282 514.0721 4 1484.865 2181.699 1212.190 2358.635 1525.308
## Jerry 2669.161 176.6376 4 2320.745 3017.578 2545.158 2923.794 2552.218
## Mickey 2406.509 263.5791 4 2058.092 2754.926 2130.413 2656.553 2207.614
## Phil 2909.658 275.1842 4 2561.242 3258.075 2565.683 3218.583 2776.782
## Q50 Q75
## Bill 2352.545 2505.925
## Bob 1670.570 1865.055
## Donna 1881.152 2189.126
## Jerry 2603.847 2720.790
## Mickey 2419.535 2618.430
## Phil 2927.183 3060.060
##
## $comparison
## NULL
##
## $groups
## yield groups
## Phil 2909.658 a
## Jerry 2669.161 ab
## Mickey 2406.509 b
## Bill 2296.268 bc
## Donna 1833.282 cd
## Bob 1667.018 d
##
## attr(,"class")
## [1] "group"
<IP_ADDRESS> Contrasts
Contrasts should be based on specific questions we want to ask of the data. In this case, there are two questions we would like to answer with contrasts.
First, cultivars “Donna” and “Bob” are from a line of canola called “PITB.” Sometimes they perform well, but other times their performance can be rather discordant. Our first question is, do cultivars from the PITB line perform worse than cultivars from other lines.
Cultivars “Mickey” and “Bill” are from the “FOTM” line of cultivars. They regularly perform better than cultivars with “PITB” pedigrees. But in bad seasons they have a field performance that has been likened to “sneakers in a drier.” Meanwhile, cultivars “Phil” and “Jerry,” from the DKSTR line, under appropriate growing conditions, have a performance potential that is almose unlimited. Our second question, then, is whether cultivars from the DKSTR outperform those from the FOTM line.
<IP_ADDRESS> Figuring Out Which Coefficients to Use
To answer these questions, we need to define our coefficients. The order of the coefficients must reflect the order of the factor levels in R. To determine this order we use the levels() function.
levels(canola$cultivar)
## NULL
What coefficients do we use, then? Our first hypothesis is that “Bob” and “Donna” perform worse than others.
- So we will assign a -1 to both those cultivars.
Coefficients: 0 -1 -1 0 0 0
- Since we are comparing against the other four cultivars, we assign them +1:
Coefficients: +1 -1 -1 +1 +1 +1
- The coefficients of each group must sum to 1. So we will divide the coefficients for Bob and Donna by 2:
Coefficients: +1 -1/2 -1/2 +1 +1 +1
- And we will divide the coefficients for the other cultivars by 4:
Coefficients: +1/4 -1/2 -1/2 +1/4 +1/4 +1/4
- Now, let’s check our math. The coefficients for Bob and Donna should sum to -1
Coefficients: 0 -1/2 -1/2 0 0 0 = -1
- The coefficients for the other four cultivars should sum to 1.
Coefficients: +1/4 0 0 +1/4 +1/4 +1/4 = 1
- Finally, all the coefficients together should sum to zero:
Coefficients: +1/4 -1/2 -1/2 +1/4 +1/4 +1/4 = 0
<IP_ADDRESS> Telling R the Coefficients to Use
Running the linear coefficient in R requires we define a “coefficient matrix.” A matrix is a slightly more primative version of the data.frame – still akin to a table, but its rows and columns tend to be defined by numbers instead of names.
To create a coefficient matrix in R, we need to define it as follows:
K = matrix(c(insert coefficients here),1)
“c(coefficients)” tells R to fill in the coefficients across columns. The “,1” that follows tells R to put those values in the first row. That second part of the matrix, the “,1” is really important. In creating this lesson, I spent at least a half-hour trying to figure out why one of my contrasts would not work. It was because I had written it:
K_bd_vs_others = matrix(c(+1/4, -1/2, -1/2, +1/4, +1/4, +1/4))
Instead of:
K_bd_vs_others = matrix(c(+1/4, -1/2, -1/2, +1/4, +1/4, +1/4), 1)
= matrix(c(+1/4, -1/2, -1/2, +1/4, +1/4, +1/4),1)
K_bd_vs_others
K_bd_vs_others
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 0.25 -0.5 -0.5 0.25 0.25 0.25
Above is our coefficient matrix for “Bob” and “Donna” versus the others.
Now let’s create our second matrix, for “Mickey” and “Bill” versus “Phil” and “Jerry.” Let’s check the order of the factor levels again.
levels(canola$cultivar)
## NULL
Our hypothesis is that “Phil” and “Jerry” perform better than “Mickey” and “Bill,” so we will assign them the initial coefficients of 1
- So we will assign a -1 to both those cultivars.
Coefficients: 0 0 0 1 0 1
- We then assign Mickey and Bill the coefficients -1:
Coefficients: -1 0 0 +1 -1 +1
“Donna” and “Bob” are assigned the coefficient zero since they are not involved in this comparison.
- The coefficients of each group must sum to 1. So we will divide the coefficients for “Jerry” and “Phil” by 2:
Coefficients: -1 0 0 +1/2 -1 +1/2
- We do the same with “Mickey” and “Bill”:
Coefficients: -1/2 0 0 +1/2 -1/2 +1/2
- Now, let’s check our math. The coefficients for Jerry and Phil should sum to 1
Coefficients: -0 0 0 +1/2 0 +1/2 = 1
- The coefficients for Mickey and Bill should sum to -1.
Coefficients: -1/2 0 0 0 -1/2 0 = -1
Finally, all the coefficients together should sum to zero:
Coefficients: -1/2 0 0 +1/2 -1/2 +1/2 = 0
Our correlation coefficient for our second contrast, then, is:
= matrix(c(-1/2, 0, 0, +1/2, -1/2, +1/2),1)
K_jp_vs_mb K_jp_vs_mb
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] -0.5 0 0 0.5 -0.5 0.5
8.9.2 Running the Contrast
Defining the contrast coefficients is difficult. Running the contrast afterwards is relatively easy. We use the ghlt() function from the multicomp() package. “glht” stands for “generalized linear hypothesis test.”
glht() requires two arguments: a linear model, and the coefficient contrast we created above. The linear model is a little different than what we defined for the ANOVA above. It only contains the terms “0” and the treatment name:
= aov(yield ~ 0 + cultivar, data=canola) canola_contrast_model
The reason for this is because our contrast is isolating the effect of cultivar. The zero forces our contrast to calculate the actual difference between the two groups’ means (which we may like to report).
Now we can run our contrast
library(multcomp)
= glht(canola_contrast_model, linfct = K_bd_vs_others) bd_vs_others
We will use summary() to summarise these results.
summary(bd_vs_others)
##
## Simultaneous Tests for General Linear Hypotheses
##
## Fit: aov(formula = yield ~ 0 + cultivar, data = canola)
##
## Linear Hypotheses:
## Estimate Std. Error t value Pr(>|t|)
## 1 == 0 820.2 139.7 5.872 1.47e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## (Adjusted p values reported -- single-step method)
Ok, let’s review our results. The Estimate, 820.2 kg / ha, is the difference between the group means. Since we subracted the mean of Bob and Donna from the mean of the other four cultivars, we concluded that Bob and Donna as a group yielded less than the other four cultivars as a group.
The Estimate was divided by Std. Error, the standard error of the difference, to produce the t value of 5.872. The probability of observing a t-value of this size or greater, if in fact the true difference between groups was zero, is very small, 1.47 x 10^-5. The difference between groups is significant.
We conclude that cultivars from the PITB line yielded worse than other cultivars as a group.
We can now run the second contrast, Jerry and Phil versus Mickey and Bill. We again use the canola_contrast_model as our linear model, and this time, the coefficient matrix K_jp_vs_mb
= glht(canola_contrast_model, linfct=K_jp_vs_mb)
jp_vs_mb summary(jp_vs_mb)
##
## Simultaneous Tests for General Linear Hypotheses
##
## Fit: aov(formula = yield ~ 0 + cultivar, data = canola)
##
## Linear Hypotheses:
## Estimate Std. Error t value Pr(>|t|)
## 1 == 0 438.0 161.3 2.716 0.0142 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## (Adjusted p values reported -- single-step method)
The mean of Jerry and Phill was 438.0 kg/ha greater than the mean of Mickey and Bill. (Note: when you look at the Estimate, always ask yourself whether the number, based on treatment means, makes sense. The first time I ran this contrast, for example, the estimate was greater than 2000. This caused me to review my contrast coefficients, where I discovered one of the coefficients was positive when it should have been negative.)
Our t value again has a probability of occuring by chance of less than P=0.05, so the difference between the mean of Jerry and the mean of Phil and Mickey and Bill is significant. We conclude that cultivars from the DKSTR line yielded better than cultivars from the FOTM line.
8.9.3 Practice: Corn Nitrogen Source and Timing
This is a really powerful example of how contrasts can be used, not just to test significances, but generate much broader understandings than either the ANOVA or LSD/HSD alone would allow. Our practice data set is from a nitrogen management trial was conducted near Whitehouse, Ohio, to compare 7 nitrogen management strategies in corn. All treatments (other than the control) provided 200 units (lbs) total N during the growing season.
= read.csv("data-unit-8/exercise_data/corn_nitrogen_source_timing.csv")
n_source_timing head(n_source_timing)
## trt yield block B Error
## 1 control 147.9109 B1 -4 3.9108674
## 2 aa_pre 178.1029 B1 -4 -1.8970790
## 3 aa_post 173.5400 B1 -4 5.5400054
## 4 aa_pre_post 184.2516 B1 -4 -2.7483991
## 5 uan_pre 170.4471 B1 -4 -0.5529385
## 6 uan_post 165.4751 B1 -4 -1.5249071
Lets look at the seven levels of nitrogen treatment:
levels(n_source_timing$trt)
## NULL
Treatment abbreviations are: “aa” = anhydrous amonia, “uan” = urea ammonium nitrate, “pre” = pre-plant, and “post” = post-emergence (sidedress).
<IP_ADDRESS> ANOVA
First, let’s look at our analysis of variance:
= aov(yield ~ block + trt, data=n_source_timing)
corn_nitrogen_model summary(corn_nitrogen_model)
## Df Sum Sq Mean Sq F value Pr(>F)
## block 3 283 94.3 2.414 0.1
## trt 6 3421 570.1 14.591 4.81e-06 ***
## Residuals 18 703 39.1
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Our nitrogen treatments are significantly different. What does the least significant test tell us differences among treatment means?
= LSD.test(corn_nitrogen_model, "trt")
lsd $groups lsd
## yield groups
## aa_pre_post 191.2750 a
## aa_pre 181.7686 b
## uan_pre_post 179.3731 b
## uan_pre 175.9014 bc
## aa_post 174.7789 bc
## uan_post 167.9677 c
## control 153.2038 d
We have four questions: 1) is the mean yield of the six nitrogen treatments significantly different from the untreated control?
is the mean yield of preplant nitrogen treatments significantly different from the mean of postemergence nitrogen treatments?
is the mean yield of anhydrous ammonia treatments significantly different from the mean of urea ammonium nitrate treatments?
is the mean yield of split application treatments significantly different from the mean of single application treatments?
<IP_ADDRESS> Nitrogen Treatments vs Control
Lets walk through this one together. What contrast coefficients do we use? Lets first double-check the order of factor levels (treatments):
levels(n_source_timing$trt)
## NULL
So we will assign -1 to the control treatment and 1 to all other treatments:
coefficients: 1 1 1 -1 1 1 1
The coefficients assigned to each group should add up to an absolute value. Since there are six nitrogen treatments, we need to divide each of their coefficients by six.
coefficients: 1/6 1/6 1/6 -1 1/6 1/6 1/6
Now, if we check, we will see the coefficients assigned to the six nitrogen treatments sum to 1, the control coefficient is zero, and the sum of all coefficients is 0.
Now let’s define our first contrast coefficient matrix by filling in the matrix below:
= matrix(c(1/6, 1/6, 1/6, -1, 1/6, 1/6, 1/6), 1) # don't forget the ", 1" after the coefficients! K_n_vs_control
We need to define our nitrogen contrast model. Remember, the model only has 0 and the factor name on the right side of the equation. Complete our equation below.
= aov(yield ~ 0 + trt, data=n_source_timing) n_contrast_model
Finally, run the contrast using the glht() and summary() functions below:
= glht(n_contrast_model, linfct=K_n_vs_control)
n_vs_control summary(n_vs_control)
##
## Simultaneous Tests for General Linear Hypotheses
##
## Fit: aov(formula = yield ~ 0 + trt, data = n_source_timing)
##
## Linear Hypotheses:
## Estimate Std. Error t value Pr(>|t|)
## 1 == 0 25.307 3.701 6.838 9.26e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## (Adjusted p values reported -- single-step method)
What do you conclude?
<IP_ADDRESS> Preplant vs Postemergence Treatments
Compare the two pre-plant treatments ("_pre“) to the two post-emergence (”_post") treatments. Your results should look like:
= matrix(c(1/2,-1/2,0,0,1/2,-1/2,0),1)
K_pre_vs_post = glht(n_contrast_model, K_pre_vs_post)
pre_vs_post summary(pre_vs_post)
##
## Simultaneous Tests for General Linear Hypotheses
##
## Fit: aov(formula = yield ~ 0 + trt, data = n_source_timing)
##
## Linear Hypotheses:
## Estimate Std. Error t value Pr(>|t|)
## 1 == 0 -7.462 3.427 -2.178 0.041 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## (Adjusted p values reported -- single-step method)
Simultaneous Tests for General Linear Hypotheses
Fit: aov(formula = yield ~ 0 + trt, data = n_source_timing)
Linear Hypotheses:
Estimate Std. Error t value Pr(>|t|)
1 == 0 -7.462 3.427 -2.178 0.041 *
Signif. codes: 0 ‘’ 0.001 ‘’ 0.01 ‘’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Adjusted p values reported – single-step method)
<IP_ADDRESS> Anhydrous Ammonian vs Urea Ammonium Nitrate
Compare the three anhydrous ammonia (“aa_”) treatments to the three urea ammonium nitrate ("_uan") treatments. Your results should look like:
= matrix(c(1/3,1/3,1/3,0,-1/3,-1/3,-1/3),1)
K_aa_vs_uan = glht(n_contrast_model, K_aa_vs_uan)
aa_vs_uan summary(aa_vs_uan)
##
## Simultaneous Tests for General Linear Hypotheses
##
## Fit: aov(formula = yield ~ 0 + trt, data = n_source_timing)
##
## Linear Hypotheses:
## Estimate Std. Error t value Pr(>|t|)
## 1 == 0 8.193 2.798 2.929 0.00803 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## (Adjusted p values reported -- single-step method)
Simultaneous Tests for General Linear Hypotheses
Fit: aov(formula = yield ~ 0 + trt, data = n_source_timing)
Linear Hypotheses:
Estimate Std. Error t value Pr(>|t|)
1 == 0 8.193 2.798 2.929 0.00803 **
Signif. codes: 0 ‘’ 0.001 ‘’ 0.01 ‘’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Adjusted p values reported – single-step method)
<IP_ADDRESS> Split vs Single Applications
Compare the two split application ("_pre_post“) treatments to the four single-application (”_pre" or "_post") treatments. Your results should look like:
= matrix(c(-1/4, -1/4, 1/2,0,-1/4,-1/4,1/2),1)
K_split_vs_single = glht(n_contrast_model, K_split_vs_single)
split_vs_single summary(split_vs_single)
##
## Simultaneous Tests for General Linear Hypotheses
##
## Fit: aov(formula = yield ~ 0 + trt, data = n_source_timing)
##
## Linear Hypotheses:
## Estimate Std. Error t value Pr(>|t|)
## 1 == 0 10.220 2.967 3.444 0.00243 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## (Adjusted p values reported -- single-step method)
Simultaneous Tests for General Linear Hypotheses
Fit: aov(formula = yield ~ 0 + trt, data = n_source_timing)
Linear Hypotheses:
Estimate Std. Error t value Pr(>|t|)
1 == 0 10.220 2.967 3.444 0.00243 **
Signif. codes: 0 ‘’ 0.001 ‘’ 0.01 ‘’ 0.05 ‘.’ 0.1 ‘ ’ 1 (Adjusted p values reported – single-step method)
attr(,“class”) [1] “group”
8.10 Exercise: Means Tables
Rarely have I seen a statistics course that addresses how to present your summarise your data for others to use. Sure, you learn about tests and P-values. But how do you summarise your data for a report publication or presentation? I don’t want to go too deep in the weeds, but I want you to be aware how to organize your data, and how R can be used to present it.
8.10.1 Case Study: Corn Nitrogen Source and Timing
This is the same dataset we used for practice in the linear contrasts exercise. Seven N fertilizer treatments (6 combinations of source and timing, plus one unfertilized control) were tested in a randomized complete block trial near Whitehouse, Ohio.
= read.csv("data-unit-8/exercise_data/corn_nitrogen_source_timing.csv")
corn_n head(corn_n)
## trt yield block B Error
## 1 control 147.9109 B1 -4 3.9108674
## 2 aa_pre 178.1029 B1 -4 -1.8970790
## 3 aa_post 173.5400 B1 -4 5.5400054
## 4 aa_pre_post 184.2516 B1 -4 -2.7483991
## 5 uan_pre 170.4471 B1 -4 -0.5529385
## 6 uan_post 165.4751 B1 -4 -1.5249071
<IP_ADDRESS> Calculating Means
We can quicly calculate means using the LSD.test() function from the agricolae package.
= aov(yield ~ block + trt, data=corn_n)
corn_n_model summary(corn_n_model)
## Df Sum Sq Mean Sq F value Pr(>F)
## block 3 283 94.3 2.414 0.1
## trt 6 3421 570.1 14.591 4.81e-06 ***
## Residuals 18 703 39.1
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
library(agricolae)
= LSD.test(corn_n_model, "trt")
lsd lsd
## $statistics
## MSerror Df Mean CV t.value LSD
## 39.07036 18 174.8955 3.573922 2.100922 9.285785
##
## $parameters
## test p.ajusted name.t ntr alpha
## Fisher-LSD none trt 7 0.05
##
## $means
## yield std r LCL UCL Min Max Q25
## aa_post 174.7789 3.277009 4 168.2129 181.3449 170.7412 177.8955 172.8403
## aa_pre 181.7686 7.217879 4 175.2026 188.3347 174.6550 191.2908 177.2409
## aa_pre_post 191.2750 5.197261 4 184.7089 197.8410 184.2516 196.7370 189.6756
## control 153.2038 7.242153 4 146.6378 159.7699 147.6271 163.0634 147.8399
## uan_post 167.9677 1.734666 4 161.4016 174.5337 165.4751 169.4693 167.5775
## uan_pre 175.9014 12.448641 4 169.3354 182.4675 162.5066 191.5746 168.4619
## uan_pre_post 179.3731 5.336127 4 172.8070 185.9391 171.7472 184.1562 178.2194
## Q50 Q75
## aa_post 175.2394 177.1780
## aa_pre 180.5644 185.0921
## aa_pre_post 192.0556 193.6550
## control 151.0624 156.4262
## uan_post 168.4631 168.8533
## uan_pre 174.7623 182.2018
## uan_pre_post 180.7945 181.9482
##
## $comparison
## NULL
##
## $groups
## yield groups
## aa_pre_post 191.2750 a
## aa_pre 181.7686 b
## uan_pre_post 179.3731 b
## uan_pre 175.9014 bc
## aa_post 174.7789 bc
## uan_post 167.9677 c
## control 153.2038 d
##
## attr(,"class")
## [1] "group"
We can create a separate table with just the $groups section:
=lsd$groups
trt_means trt_means
## yield groups
## aa_pre_post 191.2750 a
## aa_pre 181.7686 b
## uan_pre_post 179.3731 b
## uan_pre 175.9014 bc
## aa_post 174.7789 bc
## uan_post 167.9677 c
## control 153.2038 d
One thing we need to fix are the row_names. These are our treatment names and we want them in a column for our final table. The rownames_to_column() argument quickly fixes this (and is a trick it took me quite a while to figure out!). The rownames_to_column function takes one argument, vars = "", which tells R what to name the new column.
= trt_means %>%
table_w_trt_column rownames_to_column(var = "trt")
table_w_trt_column
## trt yield groups
## 1 aa_pre_post 191.2750 a
## 2 aa_pre 181.7686 b
## 3 uan_pre_post 179.3731 b
## 4 uan_pre 175.9014 bc
## 5 aa_post 174.7789 bc
## 6 uan_post 167.9677 c
## 7 control 153.2038 d
<IP_ADDRESS> Additional Statistics to Include
It would be good to add some additional statistics to this, particularly the standard error of the difference and the number of replicatons. We can calculate the standard error of the difference from the Error Mean Square given in the LSD output. First, let’s start by retrieving the error mean square from the $statistics section of our lsd output. This is a case where we can use multiple dollar signs to drill down through the list and dataframe within the list, to get to the individual statistic.
= lsd$statistics$MSerror
ems ems
## [1] 39.07036
Here is another important trick. Analyses of variance and LSD outputs rarely report the standard error of the difference. But we can quickly calculate it if we know the error mean square and the number of replicates:
SED = sqrt(2 *EMS / r)
Where r is the number of replicates. We will set r as a variable and call it into the equation (that way we can call it into our table later as well.)
= 4
r
= sqrt(2 * ems/r)
sed sed
## [1] 4.419862
Now we can add the standard error of the difference and the number of replicates to the bottom of the column yield. We will put the name of the statistic in the “treatment” column and the value of the statistic in the “yield” column. All we need to do is use the add_row() function and tell R what the value of treatment and yield are in this new row.
= table_w_trt_column %>%
table_w_stats add_row(trt="SED", yield=sed) %>%
add_row(trt="Number of Reps", yield=r)
table_w_stats
## trt yield groups
## 1 aa_pre_post 191.274962 a
## 2 aa_pre 181.768650 b
## 3 uan_pre_post 179.373088 b
## 4 uan_pre 175.901428 bc
## 5 aa_post 174.778906 bc
## 6 uan_post 167.967676 c
## 7 control 153.203813 d
## 8 SED 4.419862 <NA>
## 9 Number of Reps 4.000000 <NA>
<IP_ADDRESS> Making a Presentation-Quality Table
At this point, our table content is complete. If we are going to include this in a document or presentation, however, we need to spruce it up a little. While there are multiple packages to create tables in R, I think that the gt table is one you are most likely to use.
We’ll start loading the gt package, then using the gt() function to flow in the means table we have created above. This will create a preliminary table.
library(gt)
= gt(table_w_stats)
gt_tbl
gt_tbl
| trt | yield | groups |
|---|---|---|
| aa_pre_post | 191.274962 | a |
| aa_pre | 181.768650 | b |
| uan_pre_post | 179.373088 | b |
| uan_pre | 175.901428 | bc |
| aa_post | 174.778906 | bc |
| uan_post | 167.967676 | c |
| control | 153.203813 | d |
| SED | 4.419862 | NA |
| Number of Reps | 4.000000 | NA |
<IP_ADDRESS>.1 Choose Decimal Places
One of the first things we notice is that the means in our middle column, yield, has have way too many decimal places. We can correct this with the fmt_number() function. This function takes two arguments. First, “columns - vars(yield) tells R which column to format. The second argument,”decimals=1", tells the number of decimal places to round to.
%>%
gt_tbl fmt_number(columns = vars(yield), decimals = 1)
| trt | yield | groups |
|---|---|---|
| aa_pre_post | 191.3 | a |
| aa_pre | 181.8 | b |
| uan_pre_post | 179.4 | b |
| uan_pre | 175.9 | bc |
| aa_post | 174.8 | bc |
| uan_post | 168.0 | c |
| control | 153.2 | d |
| SED | 4.4 | NA |
| Number of Reps | 4.0 | NA |
<IP_ADDRESS>.2 Adjust Table Width
Our table looks a little skinny, so let’s adjust the width. We can do this with the cols_width function. The “everything() argument tells R to set each of the columns to the same width.”~200px" sets the width at 200px.
%>%
gt_tbl fmt_number(columns = vars(yield), decimals = 1) %>%
cols_width(everything() ~ "200px")
| trt | yield | groups |
|---|---|---|
| aa_pre_post | 191.3 | a |
| aa_pre | 181.8 | b |
| uan_pre_post | 179.4 | b |
| uan_pre | 175.9 | bc |
| aa_post | 174.8 | bc |
| uan_post | 168.0 | c |
| control | 153.2 | d |
| SED | 4.4 | NA |
| Number of Reps | 4.0 | NA |
<IP_ADDRESS>.3 Align Columns
We use the cols_align() function to align our columns. The first argument to this function, “cols_align =,” specifies whether the column(s) should be aligned to the left, center, or right. The second argument, “columns = vars()” tells R which columns to which the alignment should be applied.
%>%
gt_tbl fmt_number(columns = vars(yield), decimals = 1) %>%
cols_width(everything() ~ "200px") %>%
cols_align(align = "left", columns = vars(trt)) %>%
cols_align(align = "center", columns = vars(yield, groups))
| trt | yield | groups |
|---|---|---|
| aa_pre_post | 191.3 | a |
| aa_pre | 181.8 | b |
| uan_pre_post | 179.4 | b |
| uan_pre | 175.9 | bc |
| aa_post | 174.8 | bc |
| uan_post | 168.0 | c |
| control | 153.2 | d |
| SED | 4.4 | NA |
| Number of Reps | 4.0 | NA |
<IP_ADDRESS>.4 Assign Formal Column Headers
While it is great to use simple column names when we are processing our data, when we go to present a table we should use more formal column headings. We use the function cols_label(). We specify the current column name and desired name in our argument. For example, the current name of the left column is trt, but we want to rename it “Treatment.”
The second argument is a little fancier. We want to rename our column so that it includes “Yield” and the measurement units, “bu/acre.” But we want to use the more scientifically accepted practice of foregoing the forward-slash “/” and instead write “bu acre-1” where the “-1” is in superscript.
To get a superscript “-1,” we need to use a couple of html arguments: “” specifices the following text is superscript; ends the superscript text.
So our text argument is “Yield (bu acre-1).” To have R render it in html, however, we need to place our text string inside the html() function.
%>%
gt_tbl fmt_number(columns = vars(yield), decimals = 1) %>%
cols_width(everything() ~ "200px") %>%
cols_align(align = "left", columns = vars(trt)) %>%
cols_align(align = "center", columns = vars(yield, groups)) %>%
cols_label(trt = "Treatment",
yield = html("Yield (bu acre<sup>-1</sup>)"),
groups = "LSD Groupings")
| Treatment | Yield (bu acre-1) | LSD Groupings |
|---|---|---|
| aa_pre_post | 191.3 | a |
| aa_pre | 181.8 | b |
| uan_pre_post | 179.4 | b |
| uan_pre | 175.9 | bc |
| aa_post | 174.8 | bc |
| uan_post | 168.0 | c |
| control | 153.2 | d |
| SED | 4.4 | NA |
| Number of Reps | 4.0 | NA |
<IP_ADDRESS>.5 Table Aesthetics
At this point, our table is essentially complete. But we would like tweak a couple of things. The first thing we want to do is remove those “NA”s and just leave those cells blank. The fmt_missing() function will take care of that for us. The first argument, “columns = TRUE,” tells R to search the entire table for occurrences of “NA.” The second argument, missing_text = "“, specifies they be replaced with a blank (”").
%>%
gt_tbl fmt_number(columns = vars(yield), decimals = 1) %>%
cols_width(everything() ~ "200px") %>%
cols_align(align = "left", columns = vars(trt)) %>%
cols_align(align = "center", columns = vars(yield, groups)) %>%
cols_label(trt = "Treatment",
yield = html("Yield (bu acre<sup>-1</sup>)"),
groups = "LSD Groupings") %>%
fmt_missing(columns = TRUE, missing_text = "")
| Treatment | Yield (bu acre-1) | LSD Groupings |
|---|---|---|
| aa_pre_post | 191.3 | a |
| aa_pre | 181.8 | b |
| uan_pre_post | 179.4 | b |
| uan_pre | 175.9 | bc |
| aa_post | 174.8 | bc |
| uan_post | 168.0 | c |
| control | 153.2 | d |
| SED | 4.4 | |
| Number of Reps | 4.0 |
Finally, we would like to make a couple of the lines a little wider: the top and bottom lines of the table, as well as the line under the column labels. We can do this with the tab_options() function, which then allows us to format dozens of table aspects: lines, fonts, background colors, etc. We will use a table of “trio.border” arguments to widen those three lines.
%>%
gt_tbl fmt_number(columns = vars(yield), decimals = 1) %>%
cols_width(everything() ~ "200px") %>%
cols_align(align = "left", columns = vars(trt)) %>%
cols_align(align = "center", columns = vars(yield, groups)) %>%
cols_label(trt = "Treatment",
yield = html("Yield (bu acre<sup>-1</sup>)"),
groups = "LSD Groupings") %>%
fmt_missing(columns = TRUE, missing_text = "") %>%
tab_options(table.border.top.width = 4,
table.border.bottom.width = 4,
column_labels.border.bottom.width = 4)
| Treatment | Yield (bu acre-1) | LSD Groupings |
|---|---|---|
| aa_pre_post | 191.3 | a |
| aa_pre | 181.8 | b |
| uan_pre_post | 179.4 | b |
| uan_pre | 175.9 | bc |
| aa_post | 174.8 | bc |
| uan_post | 168.0 | c |
| control | 153.2 | d |
| SED | 4.4 | |
| Number of Reps | 4.0 |
<IP_ADDRESS>.6 Saving Your Table
Finally, we can assign our table to an object, “corn_n_gt,” and save is as an .html file to use it in other documents.
= gt_tbl %>%
corn_n_gt fmt_number(columns = vars(yield), decimals = 1) %>%
cols_width(everything() ~ "200px") %>%
cols_align(align = "left", columns = vars(trt)) %>%
cols_align(align = "center", columns = vars(yield, groups)) %>%
cols_label(trt = "Treatment",
yield = html("Yield (bu acre<sup>-1</sup>)"),
groups = "LSD Groupings") %>%
fmt_missing(columns = TRUE, missing_text = "") %>%
tab_options(table.border.top.width = 4,
table.border.bottom.width = 4,
column_labels.border.bottom.width = 4)
gtsave(corn_n_gt, "corn_n_means_table.html")
We could go on and on, but that is enough for now, and should get you pretty far. More information on the gt() package can be found at: https://gt.rstudio.com/index.html.
8.10.2 Practice: Canola
= read.csv("data-unit-8/exercise_data/canola_gd.csv")
canola head(canola)
## block cultivar yield
## 1 1 Bob 1508.424
## 2 2 Bob 1962.072
## 3 3 Bob 1364.861
## 4 4 Bob 1832.715
## 5 1 Donna 1212.190
## 6 2 Donna 1629.680
Since the focus of this exercise is table-building, I will generate the initial lsd means table for you.
library(agricolae)
= aov(yield ~ block + cultivar, data = canola)
canola_model
= LSD.test(canola_model, "cultivar")
lsd_canola lsd_canola
## $statistics
## MSerror Df Mean CV t.value LSD
## 109086 17 2296.983 14.37894 2.109816 492.7357
##
## $parameters
## test p.ajusted name.t ntr alpha
## Fisher-LSD none cultivar 6 0.05
##
## $means
## yield std r LCL UCL Min Max Q25
## Bill 2296.268 326.6421 4 1947.851 2644.685 1865.903 2614.081 2142.888
## Bob 1667.018 277.4666 4 1318.601 2015.435 1364.861 1962.072 1472.533
## Donna 1833.282 514.0721 4 1484.865 2181.699 1212.190 2358.635 1525.308
## Jerry 2669.161 176.6376 4 2320.745 3017.578 2545.158 2923.794 2552.218
## Mickey 2406.509 263.5791 4 2058.092 2754.926 2130.413 2656.553 2207.614
## Phil 2909.658 275.1842 4 2561.242 3258.075 2565.683 3218.583 2776.782
## Q50 Q75
## Bill 2352.545 2505.925
## Bob 1670.570 1865.055
## Donna 1881.152 2189.126
## Jerry 2603.847 2720.790
## Mickey 2419.535 2618.430
## Phil 2927.183 3060.060
##
## $comparison
## NULL
##
## $groups
## yield groups
## Phil 2909.658 a
## Jerry 2669.161 ab
## Mickey 2406.509 b
## Bill 2296.268 bc
## Donna 1833.282 cd
## Bob 1667.018 d
##
## attr(,"class")
## [1] "group"
First, extract the $groups table from the canola_lsd output.
= lsd_canola$groups canola_means
Next, we need to convert our row names to a column
= canola_means %>%
canola_table_w_trt_column rownames_to_column(var="cultivar")
canola_table_w_trt_column
## cultivar yield groups
## 1 Phil 2909.658 a
## 2 Jerry 2669.161 ab
## 3 Mickey 2406.509 b
## 4 Bill 2296.268 bc
## 5 Donna 1833.282 cd
## 6 Bob 1667.018 d
Finally, we need to calculate the SED and number of reps and add those to the table
= 4
r = lsd_canola$statistics$MSerror
MSE
= sqrt(2*MSE/r)
SED
= canola_table_w_trt_column %>%
canola_table_w_stats add_row(cultivar="SED", yield=SED, groups="") %>%
add_row(cultivar="Number of Reps", yield=r, groups="")
canola_table_w_stats
## cultivar yield groups
## 1 Phil 2909.6584 a
## 2 Jerry 2669.1615 ab
## 3 Mickey 2406.5090 b
## 4 Bill 2296.2682 bc
## 5 Donna 1833.2820 cd
## 6 Bob 1667.0181 d
## 7 SED 233.5444
## 8 Number of Reps 4.0000
At this point, the easiest way for you to make your table is to tweak the code below (this is why coding is so powerful).
You will need to:
- change the name of the data.frame from which you are building the table (line 1)
- change the number of decimals in the fmt_number table to zero (line 2)
- substitute “cultivar” for treatment wherever it occurs in the code (lines 3 and 5)
- change the formal heading for the first column from “Treatment” to “Cultivar”
= gt(canola_table_w_trt_column) %>%
canola_means_table fmt_number(columns = vars(yield), decimals = 1) %>%
cols_width(everything() ~ "200px") %>%
cols_align(align = "left", columns = vars(cultivar)) %>%
cols_align(align = "center", columns = vars(yield, groups)) %>%
cols_label(cultivar = "Cultivar",
yield = html("Yield (bu acre<sup>-1</sup>)"),
groups = "LSD Groupings")
canola_means_table
| Cultivar | Yield (bu acre-1) | LSD Groupings |
|---|---|---|
| Phil | 2,909.7 | a |
| Jerry | 2,669.2 | ab |
| Mickey | 2,406.5 | b |
| Bill | 2,296.3 | bc |
| Donna | 1,833.3 | cd |
| Bob | 1,667.0 | d |
8.10.3 Practice: Broccoli LSD
Common bean was grown with organic practices following four previous crops.
= read.csv("data-unit-8/exercise_data/common_bean.csv")
bean head(bean)
## block prev_crop fresh_weight
## 1 1 conv_broccoli 52.15924
## 2 1 green_manure 49.82191
## 3 1 organic_broccoli 36.80926
## 4 1 fallow 54.05342
## 5 2 conv_broccoli 52.79726
## 6 2 fallow 47.68364
<IP_ADDRESS> Create the LSD Groups table
library(agricolae)
# model statement
# LSD.test
# extract groups table from lsd output
# use rownames_to_column to create new column with rownames
<IP_ADDRESS> Prepare the table contents
# r = number of reps
# extract MSE from lsd output
# calculate SED
# use add_rows to add SED and r to lsd groups table
<IP_ADDRESS> Create the gt() table
# recycle table from above:
# change data.frame you feed to gt (line 1)
# adjust decimals as appropriate (line 2)
# change variable references for treatment and, if appropriate, yield (lines 3-5)
# change column labels (lines 5-7)
#


Thank you for selecting Symbol Technologies, Inc as your mobility solution tool provider. The Symbol Adaptive Printer Driver (APD) provides a simple way to interface application software to virtually any portable printer.
For the latest software updates, go to the Developer & Software Tools page on the Support Central Web site at:
For more information on Symbol enterprise mobility devices, please visit:
Thank you,
The Software Development Team
Version 2.19.06
- Fixing of issue printing with StoneStreetOne Bluetooth stack COM ports greater than ten.
- Fixing of issue , if other application use StoneStreetOne UserCOMPorts.
Version 2.19.05
- Fixing of issue not working with Microsoft Bluetooth Stack in devices MC9500 , MC3100.
Version 2.19.04
- Correction of Version information, return by API Call.
Version 2.19.03
- Fixing issue of buffer overflow in template file reading.
- Stop dialog popup while connecting to two different printers.
- Notify status of the printer at the time of set printer ( This issue is addressed in sample page)
Version 2.19.01
- Validation of Blue Tooth address.
- Fixing issue of not reconnecting after disconnecting from printer.
- Add compatibility to MC75 devices
- Support to BT address input with colon.
Version 2.19
- Support StoneStreet Bluetooth stack with BTExplorer Version. 1.2.4 Build 7784 and above
- APD itself picks the available bluetooth driver StoneStreet, Socket or Microsoft
Last revised: August 5, 2011


Mr. Kam
Mr. O'Donnell
COMPUTER SCIENCE AT IS 220, THE JOHN J. PERSHING SCHOOL, BROOKLYN, NEW YORK
SCRATCH
HTML
ROBOTICS
CSS


Welcome to Edinburgh Castle, you are stuck and need to escape quickly. The question is, what are you escaping from?
Good luck! Click the picture of the castle below to play!
Your objective is to escape.


New ‘Everyone Can Code’ Program Supports Younger Students
by Andrew OrrApple has redesigned its ‘Everyone Can Code’ curriculum to add more support for elementary and middle school students.
New Everyone Can Code
The new curriculum builds on what it already offers: Interactive puzzles, guides, and activities to make coding fun for younger students. Apple released a new eBook called Everyone Can Code Puzzles that uses Swift Playgrounds. There is also a companion teacher guide.
It also integrates Apple’s Everyone Can Create guides to help students explore drawing, music, video, and photos. For more advanced students the Develop in Swift curriculum will give students tools and techniques to quality for high-demand and high-skill jobs.
Computer Science Week
To celebrate Computer Science Week Apple retail stores will increase its Today at Apple coding sessions from December 1 to December 15. Certain stores will also offer special sessions, like a new Coding Lab with the Helpsters for preschool-age kids. Helpsters is a new show for Apple TV+ from the makers of Sesame Street.
Further Reading:
[Construction Begins of Austin, Texas Apple Campus]
[Review: Photoshop for iPad Isn’t Great and Hopefully Improves]


Psalms 60:6
God hath spoken in his holiness; I will rejoice, I will divide Shechem, and mete out the valley of Succoth.


Enumerations
The following enumerations are available globally.
-
The version syntax optionas that can be used when defining a dependency instance.
See moreDeclaration
Swift
public enum DependencyVersionType: CustomStringConvertible
-
Defines wheather a library should be linked to it’s clients.
See moreDeclaration
Swift
public enum LibraryLinkingType: String
-
Designates which package manager the provider is for.
See moreDeclaration
Swift
public enum ProviderType: String


Fractals!
Instructions
Try clicking the different render modes at the bottom, then click Increment to step the model forward one iteration. But watch out, it can get slow for a large iteration number.
Resolution
Iterations
Render mode
Mandelbrot
Buddhabrot
Partibrot
Linebrot
Increment
Play
Stop
Clear
Paint Mode


{% extends "trix_student/base.django.html" %} {% load i18n %} {% block title %}{% trans "Trix - student" %}{% endblock title %} {% block body %}
{% trans "Trix is a teaching platform to help you work more systematic towards your goal, resulting in a better learning outcome." %}
{{ course.description }}


Lead the mobile area and develop together with my development team cross-platform(Flutter) mobile applications that integrate API Rest - NodeJs/MongoDB together with blockchain and Fireabase.
Develop cross-platform mobile applications using technologies such as Flutter, NodeJs, Firebase according to the guidelines proposed by the company.
Develop and lead the process of cross-platform mobile and web applications using high-level technologies such as Angular, .Net Core, Xamarin, Flutter, Firebase.
Develop and lead the process of cross-platform mobile and web applications using high-level technologies such as Angular, NodeJs, Ionic, Flutter, Laravel, JavaEE, ReactJS.
Development and analysis of multiplatform web and mobile applications using high-level technologies such as Angular, Ionic, JavaEE, Spring boot, OracleDB.
Development and analysis of high-level multi-platform mobile and web applications. technologies like Angular, Ionic, NodeJs, Spring boot, H2.
I have worked with this tech for 4 years using it in personal projects and high level companies.
I have worked with this tech for 4 years using it in personal projects and high level companies.
I have worked with this tech for 4 years using it in personal projects and high level companies.
I have worked with this tech for 4 years using it in personal projects and high level companies.
I have worked with this tech for 1 year using it in personal projects and high level companies.
I have worked with this tech for 4 years using it in personal projects and high level companies.
I have worked with this tech for 4 years using it in personal projects and high level companies.
I have worked with this tech for 2 years using it in personal projects and high level companies.
I have worked with this tech for 2 years using it in personal projects and high level companies.
I have worked with this tech for 4 years using it in personal projects and high level companies.
I have worked with this tech for 4 years using it in personal projects and high level companies.
I have worked with this tech for 2 years using it in personal projects and high level companies.
I have worked with this tech for 1 year using it in personal projects and high level companies.
I have worked with this tech for 4 years using it in personal projects and high level companies.
I have worked with this tech for 2 years using it in personal projects and high level companies.
I have worked with this tech for 2 years using it in personal projects and high level companies.
I have worked with this tech for 1 year using it in personal projects and high level companies.
I have worked with this tech for 1 year using it in personal projects and high level companies.
I have worked with this tech for 2 years using it in personal projects and high level companies.
I have worked with this tech for 1 year using it in personal projects and high level companies.
I have worked with this tech for 1 year using it in personal projects and high level companies.
I have worked with this tech for 1 year using it in personal projects and high level companies.
I have worked with this tech for 1 year using it in personal projects and high level companies.
I have worked with this tech for 4 years using it in personal projects and high level companies.
Fully integrated software that allows you to control the operation from planning to executing economic and business activities.
Software for the management of private transport exclusively for women.
Software for the labor management of a woman giving a private transport exclusively for women.
Software for the management of shipments and parcels with an integrated tracking system.
Software dedicated to information management among a group of dermatology companies in Dallas TX.
Software dedicated to sharing multimedia content based on Christian building themes.
Software dedicated to user management within a business environment.
Software dedicated to the management of statistical data for a group of companies.
Software for the management of free virtual education and generation of educational multimedia content.
Software offered as a digital green food display case.
Software in charge of reporting risk situations to emergency institutions.
Software for the management and operational control of a company.
Phone
Website
Address
+57<PHONE_NUMBER>
<EMAIL_ADDRESS>jdmartinez.com
El Cerrito Valle
del Cauca. Colombia


| daisy {cluster} | R Documentation |
Compute all the pairwise dissimilarities (distances) between observations
in the data set. The original variables may be of mixed types. In
that case, or whenever metric = "gower"
is set, a
generalization of Gower's formula is used, see ‘Details’
below.
daisy(x, metric = c("euclidean", "manhattan", "gower"), stand = FALSE, type = list(), weights = rep.int(1, p), warnBin = warnType, warnAsym = warnType, warnConst = warnType, warnType = TRUE)
x |
numeric matrix or data frame, of dimension n x p,
say. Dissimilarities will be computed
between the rows of |
metric |
character string specifying the metric to be used.
The currently available options are “Gower's distance” is chosen by metric |
stand |
logical flag: if TRUE, then the measurements in If not all columns of |
type |
list for specifying some (or all) of the types of the
variables (columns) in |
weights |
an optional numeric vector of length p(= |
warnBin, warnAsym, warnConst |
logicals indicating if the corresponding type checking warnings should be signalled (when found). |
warnType |
logical indicating if all the type checking warnings should be active or not. |
The original version of daisy
is fully described in chapter 1
of Kaufman and Rousseeuw (1990).
Compared to dist
whose input must be numeric
variables, the main feature of daisy
is its ability to handle
other variable types as well (e.g. nominal, ordinal, (a)symmetric
binary) even when different types occur in the same data set.
The handling of nominal, ordinal, and (a)symmetric binary data is
achieved by using the general dissimilarity coefficient of Gower
(1971). If x
contains any columns of these
data-types, both arguments metric
and stand
will be
ignored and Gower's coefficient will be used as the metric. This can
also be activated for purely numeric data by metric = "gower"
.
With that, each variable (column) is first standardized by dividing
each entry by the range of the corresponding variable, after
subtracting the minimum value; consequently the rescaled variable has
range [0,1], exactly.
Note that setting the type to symm
(symmetric binary) gives the
same dissimilarities as using nominal (which is chosen for
non-ordered factors) only when no missing values are present, and more
efficiently.
Note that daisy
signals a warning when 2-valued numerical
variables do not have an explicit type
specified, because the
reference authors recommend to consider using "asymm"
; the
warning may be silenced by warnBin = FALSE
.
In the daisy
algorithm, missing values in a row of x are not
included in the dissimilarities involving that row. There are two
main cases,
If all variables are interval scaled (and metric
is
not "gower"
), the metric is "euclidean", and
n_g is the number of columns in which
neither row i and j have NAs, then the dissimilarity d(i,j) returned is
sqrt(p/n_g) (p=ncol(x)) times the
Euclidean distance between the two vectors of length n_g
shortened to exclude NAs. The rule is similar for the "manhattan"
metric, except that the coefficient is p/n_g. If n_g = 0,
the dissimilarity is NA.
When some variables have a type other than interval scaled, or
if metric = "gower"
is specified, the
dissimilarity between two rows is the weighted mean of the contributions of
each variable. Specifically,
d_ij = d(i,j) = sum(k=1:p; w_k delta(ij;k) d(ij,k)) / sum(k=1:p; w_k delta(ij;k)).
In other words, d_ij is a weighted mean of
d(ij,k) with weights w_k delta(ij;k),
where w_k= weigths[k]
,
delta(ij;k) is 0 or 1, and
d(ij,k), the k-th variable contribution to the
total distance, is a distance between x[i,k]
and x[j,k]
,
see below.
The 0-1 weight delta(ij;k) becomes zero
when the variable x[,k]
is missing in either or both rows
(i and j), or when the variable is asymmetric binary and both
values are zero. In all other situations it is 1.
The contribution d(ij,k) of a nominal or binary variable to the total
dissimilarity is 0 if both values are equal, 1 otherwise.
The contribution of other variables is the absolute difference of
both values, divided by the total range of that variable. Note
that “standard scoring” is applied to ordinal variables,
i.e., they are replaced by their integer codes 1:K
. Note
that this is not the same as using their ranks (since there
typically are ties).
As the individual contributions d(ij,k) are in
[0,1], the dissimilarity d_ij will remain in
this range.
If all weights w_k delta(ij;k) are zero,
the dissimilarity is set to NA
.
an object of class "dissimilarity"
containing the
dissimilarities among the rows of x
. This is typically the
input for the functions pam
, fanny
, agnes
or
diana
. For more details, see dissimilarity.object
.
Dissimilarities are used as inputs to cluster analysis and multidimensional scaling. The choice of metric may have a large impact.
Anja Struyf, Mia Hubert, and Peter and Rousseeuw, for the original
version.
Martin Maechler improved the NA
handling and
type
specification checking, and extended functionality to
metric = "gower"
and the optional weights
argument.
Gower, J. C. (1971) A general coefficient of similarity and some of its properties, Biometrics 27, 857–874.
Kaufman, L. and Rousseeuw, P.J. (1990) Finding Groups in Data: An Introduction to Cluster Analysis. Wiley, New York.
Struyf, A., Hubert, M. and Rousseeuw, P.J. (1997) Integrating Robust Clustering Techniques in S-PLUS, Computational Statistics and Data Analysis 26, 17–37.
dissimilarity.object
, dist
,
pam
, fanny
, clara
,
agnes
, diana
.
data(agriculture) ## Example 1 in ref: ## Dissimilarities using Euclidean metric and without standardization d.agr <- daisy(agriculture, metric = "euclidean", stand = FALSE) d.agr as.matrix(d.agr)[,"DK"] # via as.matrix.dist(.) ## compare with as.matrix(daisy(agriculture, metric = "gower")) data(flower) ## Example 2 in ref summary(dfl1 <- daisy(flower, type = list(asymm = 3))) summary(dfl2 <- daisy(flower, type = list(asymm = c(1, 3), ordratio = 7))) ## this failed earlier: summary(dfl3 <- daisy(flower, type = list(asymm = c("V1", "V3"), symm= 2, ordratio= 7, logratio= 8)))


| Interface | Description |
|---|---|
| AutoCloseable |
This interface establishes the rule that all classes which implements
AutoCloseable has a mechanism of auto-closing. |
| Dataset |
Interface of recommendation database which is called conventionally dataset.
|
| ExternalQuery |
Alg is the most abstract interface for all algorithms. |
| Fetcher<E> |
Fetcher is the interface for iterating each item of an associated collection.
|
| Profiles |
This interface represents a collection of profiles.
|
| Provider |
Dataset only provides read-only operations via "get" and "fetch" methods. |
| ProviderAssoc |
Any class that implements this interface is called
provider associator which assists the provider specified by interface Provider to performs read-write operations. |
| Class | Description |
|---|---|
| AbstractDataset |
This abstract class implements partially
Dataset interface. |
| Attribute |
As a convention this class is called attribute.
|
| AttributeList |
This class represents a list of attribute, called
attribute list , as a convention. |
| AttributeValue |
This class represents the pair of attribute and value.
|
| BooleanWrapper |
This class is a wrapper of boolean value and so its main internal variable
BooleanWrapper.value is a boolean value. |
| CData |
CData represents a fragment of XML code that is not processed by browser, called CDATA. |
| Condition |
Condition is the special profile which is used in condition clause
where in SQL statement for processing database, for example. |
| DataConfig |
Configuration utility class stores configuration properties.
|
| DataDriver |
DataDriver is the final class representing specific data types such as system file or database. |
| DataDriverList |
This class contains a list of
DataDriver and provides utility method on processing such list such as adding new data driver and finding a data driver. |
| DatasetAssoc | |
| DatasetMetadata |
DatasetMetadata stores essential information about dataset represented by Dataset , for example:
minimum rating value, maximum rating value, number of items, number of users, etc. |
| DatasetUtil |
This utility class, called
dataset utility , provides utility methods processing on Dataset such as loading dataset and saving dataset. |
| Datasource |
Data source represented by
Datasource class is a special class used to point to a dataset. |
| ExternalConfig |
This class specified the configuration including external information stored in outside database different from Hudup framework database.
|
| ExternalItemInfo |
Items represent many kinds of goods such as books, clothes.
|
| ExternalRecord |
The class represents an external record stored in other database (different from the framework database).
|
| ExternalUserInfo |
User has a lot of additional information.
|
| FetcherMetadata |
This class contains additional information about
Fetcher . |
| FetcherUtil |
This utility class provides utility methods to process on
Fetcher . |
| HiddenText |
This class represents a hidden text.
|
| InterchangeAttributeMap |
This class includes both internal record represented by
InternalRecord class and external record represented by ExternalRecord class. |
| InternalRecord |
This class represents a single-value record of a
unit in framework database. |
| Item |
This class represent profile of an item, called item profile, which inherits directly
Profile . |
| ItemRating |
This class extending the
RatingVector class represents a item rating vector that contains all ratings of many users on the same item and so the internal variable RatingVector.id is item identification (item ID). |
| KBasePointer | |
| Keys |
This class represents a list of key attributes.
|
| Nominal |
This class represent nominal data type called
nominal in brief. |
| NominalList |
This class represent a list of nominal (s), called nominal list, in brief.
|
| ObjectPair<T> |
This class is the pair of key and value.
|
| ObjectVector<E extends java.io.Serializable> |
This class represents a vector of objects.
|
| Pair |
This class represents a pair of key and value.
|
| ParamSql |
This class represents a SQL statement having parameters, called
parametric SQL . |
| Pointer |
This is abstract class of pointer.
|
| Profile |
Profile is one of important data structures, like record of table in database, has a list of values. |
| PropList |
This utility class stores dictionary of entries.
|
| ProviderAssocAbstract |
This abstract class implements partially the
ProviderAssoc interface. |
| Rating |
This class represents a rating that a user gives on an item.
|
| RatingMatrix |
This class represents a rating matrix contains a set of rows and a set of columns in table form.
|
| RatingMatrixMetadata |
This class represents meta-data of rating matrix for storing additional information of rating matrix.
|
| RatingMulti |
This class is firstly similar to
Rating contains ratings of one user giving on many items or ratings of one item receiving from many users. |
| RatingTriple |
This class contains the rating that a user gives on an item, including three following parts:
The rating represented by
Rating class.
User identification (user ID).
Item identification (item ID).
|
| RatingVector |
This class represents a vector (an array) of ratings.
|
| Scanner |
This is abstract class of scanner.
|
| ServerPointer | |
| Snapshot |
This is abstract class of snapshot.
|
| SysConfig |
Note, a configuration represented by
DataConfig class stores configuration properties. |
| Unit |
Objects in framework such as
profile , item profile , user profile , rating matrix , interchanged attribute map are stored in archives (files) of entire framework. |
| UnitList |
This class represents a list of many units.
|
| User |
This class represent profile of an item, called item profile, which inherits directly
Profile . |
| UserRating |
This class extending the
RatingVector class represents a user rating vector that contains all ratings of the same user on many items and so the internal variable RatingVector.id is user identification (user ID). |
| Wrapper |
This is the wrapper of any object.
|
| Enum | Description |
|---|---|
| Attribute.Type |
This enum represents data type of this attribute.
|
| DataDriver.DataType |
DataDriver.DataType is Enum class that represents data type such as Derby database, system file, ftp file, http file, network file, Microsoft SQL Server database, MySQL database, Oracle database, PostgreSQL database. |
| PropList.ExtendedType |
Specifying extended types of values in
PropList . |


SceneJS
- extending - creating a new node type
In this example we create a new scene node type, which consists of a rotating teapot.
Learn more about creating new node types in
this tutorial