5 Fitness model
This chapter is a work in progress.
Previous chapters showed how to estimate SRNs and assortment on SRN parameters using SAMs. In this chapter, we’ll extend this basic approach to model how between-individual variation in SRN parameters affects individuals’ relative fitness, which we’ll then use to predict patterns of adaptive social evolution in the phenotype.
Our basic fitness model considers linear directional selection on each individual’s SRN intercept \(\mu_j\) and slope \(\psi_j\), as well as directional social selection due to partner SRN intercepts \(\mu'_k\) and slopes \(\psi'_k\). In addition, the magnitude of this directional selection is modulated by the interactive effects of the joint individual and partner phenotypes \(\mu_j\mu'_k\) and \(\psi_j\psi'_k\). These interactive effects reflect synergy (+) or antagonism (-) between the trait values of individuals and their social partners. In Martin and Jaeggi (2021), the fitness model is formally presented for the effects of the average social partner. For the purposes of this simulation, we instead consider a fitness model for the reproductive success of each individual and partner dyad across multiple reproductive seasons, rather than the effect of the average partner across seasons. In particular, following the within and between partner model (Ch. 4 ), we assume that individuals are sampled repeated within partners (2x) across multiple partners (x4). For the fitness model, we assume that these pairs are (serially) monogamous breeding partners measured across breeding seasons, with a single fitness measure (e.g. fledgling success) taken once per pair at the end of the breeding season. Estimating fitness effects across these repeated measures provides more power for the estimation of selection and assortment, on the assumption that fitness effects do not meaningfully vary across breeding seasons. This may be unrealistic for a variety of reasons (e.g. density-dependent effects), in which case the fitness model can be expanded with season-specific coefficients.
The model for relative fitness \(w\) measure \(i\) of individual \(j\) with partner \(k\) is therefore given as
\[w_{ijk} = \nu_0 + \beta_{N1} + \beta_{N2} + \beta_{S1} + \beta_{S2} + \beta_{I2} + \beta_{I2} \] where we. Note that the interpretation of the regression coefficients is contingent on the potentially arbitrary designation of focal and social partner sex. In this case, we treat males as focals, such that \(\boldsymbol{\beta_N}\) represent nonsocial selection gradients on male SRNs, while \(\boldsymbol{\beta_S}\) represent social selection gradients acting on males due to female mating partners. For females in this simplified context of purely monogamous reproduction, the interpretation is reversed, given that \(w_{ijk}\) is the shared fitness of both partners during their interaction. Under the further simplifying assumptions that \(\beta_{N1}=\beta_{S1}\) and \(\beta_{N2}=\beta_{S2}\), a single selection differential can be used to characterize both males and females in the population. Of course, this will often be unrealistic, and males and female partners will often have only partially shared fitness outcomes, in which case more general sex-specific response models can also be specified
\[w_{ij} = \nu_0 + \beta_{N1}\mu_j + \beta_{N2}\psi_j + \beta_{S1}\mu'_k + \beta_{S2}\psi'_k + \beta_{I2}(\mu_j\mu'_k) + \beta_{I2}(\psi_j\psi'_k) \] \[w'_{ik} = \nu_0 + \beta'_{N1}\mu'_k + \beta'_{N2}\psi'_k + \beta'_{S1}\mu_j + \beta'_{S2}\psi_j + \beta'_{I2}(\mu'_k\mu_j) + \beta'_{I2}(\psi'_k\psi_j) \] For pedagogical purposes, we simulate data under the simpler shared dyadic fitness model and will consider more complex cases in subsequent SAM extensions.
5.1 Simulate data
The initial simulation approach is identical to previous chapters and can be reviewed there. We generate data appropriate for the within and between partner SAM, assuming that assortment occurs between dyadic partners for SRN slopes.
library(mvtnorm)
#common settings
= 4 #partners/individual
I_partner = 2 #observations/individual/seasonal partner
I_obs = I_partner*I_obs #samples/individual
I_sample
#population properties
=300 #total individuals for simulation
I=400
popmin=600
popmax= 10
ngenerations <-sample(popmin:popmax, ngenerations, replace=TRUE) #N / generation
nids= sample(seq(0.15, 0.25,by=0.05),1) #extra-pair mating
epm = sample(seq(0.4,0.6,by=0.05),1) #proportion of non-breeding / generation
nonb
#relatedness matrix
<- pedfun(popmin=popmin, popmax=popmax, ngenerations=ngenerations,
A_mat epm=epm, nonb=nonb, nids=nids, I=I, missing=FALSE)
#####################################################################
#Parameter values
#####################################################################
= 0 #global intercept
alpha_0 = -0.5 #population interaction coefficient
psi_1 = 0.5 #residual feedback coefficient (epsilon_j ~ epsilon_t-1k)
phi = 0.3 #standard deviation of SRN intercepts
SD_intercept = 0.3 #SD of SRN slopes
SD_slope = 0.3 #assortment coefficient (expressed as correlation)
r_alpha = 0.3 #genetic correlation of random intercepts and slopes
r_G = 0.3 #environmental correlation
r_E = -0.3 #residual effect correlation (epsilon_tj = epsilon_tk)
r_R = 0.3 #genetic variance of REs
V_G = 0.3 #genetic variance of REs
V_E = 1
res_V
#Random effect correlations
<- matrix(c(1,r_G,r_G,1), nrow=2, ncol=2) #mu_A, beta_A
G_cor <- c(sqrt(V_G),sqrt(V_G)) #G effect sds
G_sd <- diag(G_sd) %*% G_cor %*% diag(G_sd)
G_cov
<- matrix(c(1,r_E,r_E,1), nrow=2, ncol=2) #mu_E, beta_E
E_cor <- c(sqrt(V_E),sqrt(V_E)) #E effect sds
E_sd <- diag(E_sd) %*% E_cor %*% diag(E_sd)
E_cov
#matrices
<- G_cov %x% A_mat
G_block <- E_cov %x% diag(1,I)
E_block
#generate correlated REs
<- rmvnorm(1, mean=rep(0,I*2), sigma=G_block)
Gvalues = data.frame(matrix(Gvalues, nrow=I, ncol=2))
G_val cor(G_val)
## X1 X2
## X1 1.000000 0.198498
## X2 0.198498 1.000000
<- rmvnorm(1, mean=rep(0,I*2), sigma=E_block)
Evalues = data.frame(matrix(Evalues, nrow=I, ncol=2))
E_val cor(E_val)
## X1 X2
## X1 1.0000000 0.3342701
## X2 0.3342701 1.0000000
#combine temporary object for all SRN parameters
#use shorthand mu = 0, psi = 1
= cbind(G_val,E_val)
P colnames(P) = c("A0", "A1", "E0", "E1")
#individual phenotypic REs
#use shorthand mu = 0, psi = 1
$P0 = P$A0 + P$E0
P$P1 = P$A1 + P$E1
P
#add ID
$ID = seq(1:I)
P
library(dplyr)
library(MASS)
= list()
pairs for (j in 1:I_partner){
#male additive genetic RN slopes (x I_partner for multiple lifetime partners)
<- data.frame(P1_m = P$P1[1:(I/2)], ID_m = (1:(I/2)) )
sort.m <-sort.m[order(sort.m[,"P1_m"]),]
sort.m#female phenotypic RN slopes
<- data.frame(P1_f = P$P1[(I/2 + 1):I], ID_f = ((I/2+1):I) )
sort.f <-sort.f[order(sort.f[,"P1_f"]),]
sort.f#generate random dataset with desired rank-order correlation
<- matrix(r_alpha, ncol = 2, nrow = 2) #cor of male and female values
temp_mat diag(temp_mat) <- 1 #cor matrix
#sim values
<-MASS::mvrnorm(n = I/2, mu = c(0, 0), Sigma = temp_mat, empirical=TRUE)
temp_data1#ranks of random data
<- rank(temp_data1[ , 1], ties.method = "first")
rm <- rank(temp_data1[ , 2], ties.method = "first")
rf #induce cor through rank-ordering of RN vectors
cor(sort.m$P1_m[rm], sort.f$P1_f[rf])
#sort partner ids into dataframe
= data.frame(ID_m = sort.m$ID_m[rm], ID_f = sort.f$ID_f[rf])
partner.id = partner.id[order(partner.id[,"ID_m"]),]
partner.id #add to list
= partner.id
pairs[[j]]
}
= bind_rows(pairs)
partner.id = partner.id[order(partner.id$ID_m),]
partner.id
#put all dyads together
$dyadn = seq(1:nrow(partner.id))
partner.id
#add values back to dataframe (male and joint)
$P0m <- P$P0[match(partner.id$ID_m,P$ID)]
partner.id$P0f <- P$P0[match(partner.id$ID_f,P$ID)]
partner.id$P1m <- P$P1[match(partner.id$ID_m,P$ID)]
partner.id$P1f <- P$P1[match(partner.id$ID_f,P$ID)]
partner.id
$A0m <- P$A0[match(partner.id$ID_m,P$ID)]
partner.id$A0f <- P$A0[match(partner.id$ID_f,P$ID)]
partner.id$A1m <- P$A1[match(partner.id$ID_m,P$ID)]
partner.id$A1f <- P$A1[match(partner.id$ID_f,P$ID)]
partner.id
$E0m <- P$E0[match(partner.id$ID_m,P$ID)]
partner.id$E0f <- P$E0[match(partner.id$ID_f,P$ID)]
partner.id$E1m <- P$E1[match(partner.id$ID_m,P$ID)]
partner.id$E1f <- P$E1[match(partner.id$ID_f,P$ID)]
partner.id
#check correlation again
cor(partner.id$P1m, partner.id$P1f)
## [1] 0.3030801
#calculate mean partner phenotype for each subject
#average female for male partners
<- aggregate(P0f ~ ID_m, mean, data = partner.id)
mean_0m names(mean_0m)[2] <- "meanP0m"
<- aggregate(P1f ~ ID_m, mean, data = partner.id)
mean_1m names(mean_1m)[2] <- "meanP1m"
$meanP0m <- mean_0m$meanP0m[match(partner.id$ID_m,mean_0m$ID_m)]
partner.id$meanP1m <- mean_1m$meanP1m[match(partner.id$ID_m,mean_1m$ID_m)]
partner.id
#average male for female partners
<- aggregate(P0m ~ ID_f, mean, data = partner.id)
mean_0f names(mean_0f)[2] <- "meanP0f"
<- aggregate(P1m ~ ID_f, mean, data = partner.id)
mean_1f names(mean_1f)[2] <- "meanP1f"
$meanP0f <- mean_0f$meanP0f[match(partner.id$ID_f,mean_0f$ID_f)]
partner.id$meanP1f <- mean_1f$meanP1f[match(partner.id$ID_f,mean_1f$ID_f)]
partner.id
#number of dyads
= nrow(partner.id)
ndyad
#expand for repeated measures
$rep <- I_obs
partner.id<- partner.id[rep(row.names(partner.id), partner.id$rep),]
pair_df
#correlations
cor(partner.id$P0m, partner.id$P0f)
## [1] 0.05911671
cor(partner.id$P1m, partner.id$P0f)
## [1] 0.1148086
cor(partner.id$P0m, partner.id$P1f)
## [1] 0.05707536
cor(partner.id$P1m, partner.id$P1f)
## [1] 0.3030801
#####################################################################
#Additional effects
#####################################################################
#correlated residuals between male and females
<- matrix(c(1,r_R,r_R,1), nrow=2, ncol=2)
R_cor <- sqrt(res_V)
res_sd <- diag(c(res_sd,res_sd)) %*% R_cor %*% diag(c(res_sd,res_sd))
R_cov <-data.frame(rmvnorm(nrow(pair_df), c(0,0), R_cov))
res_ind$resAGm = res_ind$X1
pair_df$resAGf = res_ind$X2
pair_df
#####################################################################
#Simulate responses over t = {1,2} per partner
#####################################################################
#add interaction number
$turn = rep(c(1,2),ndyad)
pair_df
#average male social environment at time = 1
$turn==1,"meaneta_m"] = pair_df[pair_df$turn==1,"meanP0m"] +
pair_df[pair_df+ pair_df[pair_df$turn==1,"meanP1m"])*(pair_df[pair_df$turn==1,"P0m"])
(psi_1
#average female social environment at time = 1
$turn==1,"meaneta_f"] = pair_df[pair_df$turn==1,"meanP0f"] +
pair_df[pair_df+ pair_df[pair_df$turn==1,"meanP1f"])*(pair_df[pair_df$turn==1,"P0f"])
(psi_1
#individual prediction at t = 1
#males
#eta_j{t=1} = mu_j + psi_j*(mu_k - mu_meanK)
$turn==1,"eta_m"] = pair_df[pair_df$turn==1,"P0m"] +
pair_df[pair_df+ pair_df[pair_df$turn==1,"P1m"])*(pair_df[pair_df$turn==1,"P0f"])
(psi_1
#females
#eta_k{t=1} = mu_k + psi_k*(mu_j - mu_meanJ)
$turn==1,"eta_f"] = pair_df[pair_df$turn==1,"P0f"] +
pair_df[pair_df+ pair_df[pair_df$turn==1,"P1f"])*(pair_df[pair_df$turn==1,"P0m"])
(psi_1
#individual prediction at t = 2
#eta_j{t=2} = mu_j + psi_j*(eta_k{t=1} - eta_meanK{t=1})
$turn==2,"eta_m"] = pair_df[pair_df$turn==2,"P0m"] +
pair_df[pair_df+ pair_df[pair_df$turn==2,"P1m"])*(pair_df[pair_df$turn==1,"eta_f"])
(psi_1
#females
$turn==2,"eta_f"] = pair_df[pair_df$turn==2,"P0f"] +
pair_df[pair_df+ pair_df[pair_df$turn==2,"P1f"])*(pair_df[pair_df$turn==1,"eta_m"])
(psi_1
#add intercept and residual
$AG_m = alpha_0 + pair_df$eta_m + pair_df$resAGm
pair_df$AG_f = alpha_0 + pair_df$eta_f + pair_df$resAGf
pair_df
#add residual feedback
$turn==2,"AG_m"] = pair_df[pair_df$turn==2,"AG_m"] + phi * pair_df[pair_df$turn==1,"resAGf"]
pair_df[pair_df$turn==2,"AG_f"] = pair_df[pair_df$turn==2,"AG_f"] + phi * pair_df[pair_df$turn==1,"resAGm"] pair_df[pair_df
We can now simulate the relative fitness measure for each dyad during their interaction. We set the regression coefficients for moderate effect sizes.
#set coefficients
= 1 #relative fitness intercept
nu_0 = 0.3
beta_n1 = -0.3
beta_n2 = 0.3
beta_s1 = -0.3
beta_s2 = -0.3
beta_i1 = -0.3
beta_i2
#dyad fitness (nu_0 = 1 so that w is relative fitness w = W/W_mean for the unbiased population mean fitness)
$w_mu = nu_0 + beta_n1*pair_df$P0m + beta_n2*pair_df$P1m + beta_s1*pair_df$P0f + beta_s2*pair_df$P1f +
pair_df*(pair_df$P0m*pair_df$P0f) + beta_i2*(pair_df$P1m*pair_df$P1f)
beta_i1#remove redundant elements
<-pair_df[seq(1, nrow(pair_df), by=I_obs),"w_mu"]
w_mu
#add stochastic effects (same sd as phenotype)
= w_mu + rnorm(length(w_mu),0, res_sd) w
We’ll need additional indices for the Stan code to appropriately estimate the fitness model.
#####################################################################
#Prepare data for Stan
#####################################################################
#individual indices
= I/2 #number of males
Im = I/2 #number of females
If = (I/2)*2*4 #total observations per sex
N_sex <-pair_df$ID_m #male ID
idm<-pair_df$ID_f #female ID
idf<-idf - (Im) #index within female vector
idf<- pair_df$dyadn
dyadAG <- seq(1:ndyad)
dyadw
#partner IDs for male individuals
<-data.frame(idfocal = rep(1:(I/2)), #all partners ID
partners_mpartner1 = NA, partner2 = NA, partner3 = NA, partner4 = NA)
for(i in 1:(I/2)){partners_m[i,c(2:5)] <-partner.id[partner.id$ID_m==i,"ID_f"]}
#partner IDs for female individuals
<-data.frame(idfocal = rep((I/2+1):I), #all partners ID
partners_fpartner1 = NA, partner2 = NA, partner3 = NA, partner4 = NA)
for(i in (I/2+1):I){partners_f[i-(I/2),c(2:5)] <-partner.id[partner.id$ID_f==i,"ID_m"]}
######################
#data prep for Stan
<-
stan_data list(N_sex = N_sex, I = I, Im=Im, If = If, idm = idm, idf = idf,
partners_m = partners_m, partners_f = partners_f,
AG_m = pair_df$AG_m, AG_f = pair_df$AG_f, time = pair_df$turn, A = A_mat,
#new indices and data
Idyad=ndyad, dyadw = dyadw, idmw = partner.id$ID_m, idfw = c(partner.id$ID_f-Im), w = w)
5.2 Estimating the model
We’re now prepared to extend the Stan code for the within and between partner model to account for selection on SRN intercepts and slopes. This involves specifying the fitness model described above as an additional response model, with SRN parameters simultaneously specified on the phenotype and fitness. As described in the main text, this multi-response model will avoid various sources of statistical bias that result from estimating these models in isolation. Fortunately, in contrast to the more complex SRN model, it is quite straightforward to add the fitness model with a few additional declarations in the data
, parameters
, and model
program blocks. These changes are separated out with comments in the code below for clarity.
write("
data {
//indices and scalars used for model specification
int<lower=1> N_sex; //total aggression observations per sex (I/2 * 4 lifetime partners)
int<lower=0> I; //total individuals (M + F)
int<lower=0> Im; //number of males
int<lower=0> If; //number of females
int<lower=1> idm[N_sex]; //index of male AG observations (of length N_sex)
int<lower=1> idf[N_sex]; //index of female AG observations
int<lower=1> partners_m [Im,5]; //index of male partner IDs, first column is focal ID (1 + 4 IDs)
int<lower=1> partners_f [If,5]; //index of female partner IDs, first column is focal ID (1 + 4 IDs)
//empirical data
matrix[I,I] A; //relatedness matrix
real AG_m[N_sex]; //male aggression measurements
real AG_f[N_sex]; //female aggression measurements
real time[N_sex]; //time index (=1 for all measures)
//new fitness data
int<lower=1> Idyad; //number of dyads
int<lower=1> idmw[Idyad]; //index of male w observations
int<lower=1> idfw[Idyad]; //index of female w observations
int<lower=1> dyadw[Idyad]; //index of dyads for FS
real w[Idyad]; // dyad response
}
transformed data{
matrix[I,I] LA = cholesky_decompose(A); //lower-triangle A matrix
}
parameters {
//new fitness parameters
real nu_0; //fitness global intercept
real<lower=0, upper = 1>sd_delta; //residual of fitness
real beta_N1; //selection gradients
real beta_N2; //could also be specified as vectors
real beta_S1;
real beta_S2;
real beta_I1;
real beta_I2;
//population effects
real alpha_0; //aggression global intercept
real psi_1; //expected interaction coefficient
real beta_B; //between partner effect
real<lower=-1,upper=1> phi; //(-1,1) ensures unique solution
//no way to partition feedback when t=1
//real<lower=-1,upper=1> phi; //(-1,1) ensures unique solution
//random effects (standard deviations)
vector<lower=0, upper = 1>[2] sd_P; //phenotypic SRN mu & psi SDs
vector<lower=0, upper = 1>[2] sd_R; //male & female residual SDs
cholesky_factor_corr[2] LG; //genetic SRN correlations
cholesky_factor_corr[2] LE; //permanent environmental SRN correlations
cholesky_factor_corr[2] LR; //sex-specific residual correlations
matrix[I,2] std_devG; //individual-level unscaled G SRN deviations
matrix[I,2] std_devE; //individual-level unscaled E SRN deviations
//SRN heritability parmameters, i.e. Var(G_RN) / Var(P_RN)
//see supplementary appendix SI for further explanation of this parameter
vector<lower=0,upper=1>[2] SRN_h2;
}
transformed parameters {
vector<lower=0>[2] sd_G; //SDs of G effects (derived from sd_P)
vector<lower=0>[2] sd_E; //SDs of E effects (derived from sd_P)
matrix[I,2] SRN_P; //scaled P SRN parameter deviations
matrix[I,2] SRN_G; //scaled G SRN parameter deviations
matrix[I,2] SRN_E; //scaled E SRN parameter deviations
matrix[If, 2] partner_meanm; //average SRN parameters of males' partners
matrix[Im, 2] partner_meanf; //average SRN parameters of females' partners
//standard deviations of genetic effects
//simplified from sqrt ( total RN phenotype variance * h2 )
sd_G[1] = sd_P[1] * sqrt(SRN_h2[1]); //genetic SD for RN intercepts
sd_G[2] = sd_P[2] * sqrt(SRN_h2[2]); //genetic SD for RN slopes
//standard deviations of environmental effects (total phenotype SD * proportion environment SD)
sd_E[1] = sd_P[1] * sqrt(1 - SRN_h2[1]); //environment SD for RN intercepts
sd_E[2] = sd_P[2] * sqrt(1 - SRN_h2[2]); //environment SD for RN slopes
//matrix normal parameterization of Kronecker product between G and A
SRN_G = LA * std_devG * diag_pre_multiply(sd_G, LG)' ;
//non-centered parameterization of permanent environmental effects
SRN_E = std_devE * diag_pre_multiply(sd_E, LE)';
//phenotypic RN effects (P = G + E); here G = additive genetic effects
SRN_P = SRN_G + SRN_E;
//calculate the mean SRN parameters of each male's lifetime partners
for(i in 1:Im) partner_meanm[i] = [mean(col(SRN_P[partners_m[i,2:5]],1)),
mean(col(SRN_P[partners_m[i,2:5]],2))];
//calculate the mean SRN parameters of each female's lifetime partners
for(i in 1:If) partner_meanf[i] = [mean(col(SRN_P[partners_f[i,2:5]],1)),
mean(col(SRN_P[partners_f[i,2:5]],2))];
}
model{
//separate male and female vectors for efficiency
matrix[Im,2] SRN_Pm = SRN_P[1:Im]; //male SRN phenotypic deviations
matrix[If,2] SRN_Pf = SRN_P[(Im+1):I]; //female SRN phenotypic deviations
//separate SRN intercepts and slopes (phenotypic deviations)
vector[Im] mu_m = col(SRN_Pm,1); //SRN intercepts
vector[If] mu_f = col(SRN_Pf,1);
vector[Im] psi_m = col(SRN_Pm,2); //SRN slopes
vector[If] psi_f = col(SRN_Pf,2);
//separate mean partner SRN intercepts and slopes (deviations)
vector[Im] mu_meanm = col(partner_meanm,1); //mean partner SRN intercept for males
vector[If] mu_meanf = col(partner_meanf,1); //...for females
vector[Im] psi_meanm = col(partner_meanm,2); //mean partner SRN slope for males
vector[If] psi_meanf = col(partner_meanf,2); //...for females
//initialize vectors for constructing individual-centered linear predictors
vector[N_sex] eta_Wm; //within-individual centered male SRN trait value
vector[N_sex] eta_Wf; //within-individual centered female SRN trait value
vector[N_sex] eta_Bm; //individual male SRN trait value toward average partner
vector[N_sex] eta_Bf; //individual female SRN trait toward average partner
vector[N_sex] eta_meanm; //average SRN partner values for males
vector[N_sex] eta_meanf; //average SRN partner values for females
vector[N_sex] linpred_m; //expected value for male responses
vector[N_sex] linpred_f; //expected value for female responses
vector[N_sex] epsilon_m; //residuals for male responses
vector[N_sex] epsilon_f; //residuals for male responses
//new fitness model declarations
vector[Idyad] w_pred; //linear predictor of fitness
//Male and female aggression response model
for (n in 1:N_sex) {
//SRN trait values
//assumes that n = 1 in the context of an ongoing social interaction
//if n = 1 prior to social context, then specify eta[t=1] = mu_j instead
if (time[n]==1)
{
//within-individual centered eta
//male eta[t=1] = mu_j + psi_j*(mu_k - mu_meanK)
eta_Wm[n] = mu_m[idm[n]] + (psi_1 + psi_m[idm[n]])*(mu_f[idf[n]] - mu_meanm[idm[n]]) ;
//female eta[t=1] = mu_k + psi_k*(mu_j - mu_meanJ)
eta_Wf[n] = mu_f[idf[n]] + (psi_1 + psi_f[idf[n]])*(mu_m[idm[n]] - mu_meanf[idf[n]]);
//average individual eta
//male eta[t=1] = mu_j + psi_j*mu_k
eta_Bm[n] = (psi_1 + psi_m[idm[n]])*mu_meanm[idm[n]];
//female eta[t=1] = mu_k + psi_k*mu_j
eta_Bf[n] = (psi_1 + psi_f[idf[n]])*mu_meanf[idf[n]];
//average partner eta[t=1]
//average eta males' partners [t=1] = mu_meanK + psi_meanK*mu_j
eta_meanm[n] = mu_meanm[idm[n]] + (psi_1 + psi_meanm[idm[n]])*mu_m[idm[n]];
//average eta females' partners [t=1] = mu_meanJ + psi_meanJ*mu_k
eta_meanf[n] = mu_meanf[idf[n]] + (psi_1 + psi_meanf[idf[n]])*mu_f[idf[n]];
}
else
{
//within-individual centered eta
//male eta[t=2] = mu_j + psi_j*(eta_k[t=1] - eta_meanK[t=1])
eta_Wm[n] = mu_m[idm[n]] + (psi_1 + psi_m[idm[n]])*(eta_Wf[n-1] - eta_meanm[n-1]);
//female eta[t=2] = mu_k + psi_k*(eta_j[t=1] - eta_meanJ[t=1])
eta_Wf[n] = mu_f[idf[n]] + (psi_1 + psi_f[idf[n]])*(eta_Wm[n-1] - eta_meanf[n-1]);
//average individual eta
//male average eta[t=2] = mu_j + psi_j*eta_meanK[t=1]
eta_Bm[n] = (psi_1 + psi_m[idm[n]])*eta_meanm[n-1];
//female average eta[t=2] = mu_k + psi_k*eta_meanJ[t=1]
eta_Bf[n] = (psi_1 + psi_f[idf[n]])*eta_meanf[n-1];
//average eta males' partners [t=1] = mu_meanK + psi_meanK*mean eta_j[t-1]
eta_meanm[n] = mu_meanm[idm[n]] + (psi_1 + psi_meanm[idm[n]])*(mu_m[idm[n]] + eta_Bm[n-1]);
//female average partner eta
eta_meanf[n] = mu_meanf[idf[n]] + (psi_1 + psi_meanf[idf[n]])*(mu_f[idf[n]] + eta_Bf[n-1]);
}
//add global intercept and between-individual parameters to linear predictor
//other fixed effects can also be added here
linpred_m[n] = alpha_0 + eta_Wm[n] + beta_B*eta_Bm[n]; //+beta_B*eta_Bm[n]
linpred_f[n] = alpha_0 + eta_Wf[n] + beta_B*eta_Bf[n]; //+beta_B*eta_Bf[n]
//residual trait values
if(time[n]==1)
{
epsilon_m [n] = AG_m[n] - linpred_m[n];
epsilon_f [n] = AG_f[n] - linpred_f[n];
}
else
{
linpred_m[n] = linpred_m[n] + phi * epsilon_f[n-1];
epsilon_m[n] = AG_m[n] - linpred_m[n];
linpred_f[n] = linpred_f[n] + phi * epsilon_m[n-1];
epsilon_f[n] = AG_f[n] - linpred_f[n];
}
//correlated residuals between partners
[epsilon_m[n],epsilon_f[n]]' ~ multi_normal_cholesky([0,0], diag_pre_multiply(sd_R, LR));
}
//new fitness model
w_pred = nu_0 + beta_N1*mu_m[idmw] + beta_N2*psi_m[idmw] + beta_S1*mu_f[idfw] + beta_S2*psi_f[idfw] +
beta_I1*(mu_m[idmw].*mu_f[idfw]) + beta_I2*(psi_m[idmw].*psi_f[idfw]);
w ~ normal(w_pred, sd_delta);
//model priors
//fixed effects
alpha_0 ~ std_normal();
psi_1 ~ std_normal();
beta_B ~ std_normal();
phi ~ std_normal();
//random effects
to_vector(sd_P) ~ cauchy(0,1);
to_vector(sd_R) ~ cauchy(0,1);
LG ~ lkj_corr_cholesky(2);
LE ~ lkj_corr_cholesky(2);
LR ~ lkj_corr_cholesky(2);
to_vector(std_devG) ~ std_normal();
to_vector(std_devE) ~ std_normal();
//reaction norm heritability
to_vector(SRN_h2) ~ beta(1.2,1.2);
//new fitness priors
nu_0 ~ std_normal();
sd_delta ~ cauchy(0,1);
beta_N1 ~ std_normal();
beta_N2 ~ std_normal();
beta_S1 ~ std_normal();
beta_S2 ~ std_normal();
beta_I1 ~ std_normal();
beta_I2 ~ std_normal();
}
generated quantities{
//cor and cov matrices of SRN parameters and residuals
matrix[2,2] Gcor = LG * LG'; //G SRN correlation matric
matrix[2,2] Ecor = LE * LE'; //E SRN correlation matric
matrix[2,2] Rcor = LR * LR'; //residual correlation matrix
matrix[2,2] Rcov = diag_matrix(sd_R)*Rcor*diag_matrix(sd_R); //residual covariance
matrix[2,2] Gcov = diag_matrix(sd_G)*Gcor*diag_matrix(sd_G); //G SRN covariance
matrix[2,2] Ecov = diag_matrix(sd_E)*Ecor*diag_matrix(sd_E); //E SRN covariance
matrix[2,2] Pcov = Gcov + Ecov; //P SRN covariance
matrix[2,2] Pcor = inverse(diag_matrix(sd_P))*Pcov*inverse(diag_matrix(sd_P)); //P SRN correlation
//variances
vector<lower=0>[2] V_P = sd_P .* sd_P;
vector<lower=0>[2] V_G = sd_G .* sd_G;
vector<lower=0>[2] V_E = sd_E .* sd_E;
vector<lower=0>[2] V_R = sd_R .* sd_R;
}", "sam5_w.stan")
Depending on the sample size set during the simulation, this model will likely take 30+ min to finish sampling. The total number of iterations can be reduced to save time, but is set to a large value here to ensure sufficient effective sample sizes for some parameters.
library(rstan)
= stan_model("sam5_w.stan")
sam_5
<- sampling(sam_5, data=stan_data, init = 0, warmup=1500, iter = 3000,
stan_results5 chains=4, cores=4, control=list(adapt_delta=0.90) )
library(bayesplot)
mcmc_areas(stan_results5, pars = c( "psi_1", "nu_0", "beta_B", "beta_N1", "beta_N2",
"beta_S1", "beta_S2", "beta_I1", "beta_I2", "V_P[1]", "V_P[2]", "Gcor[2,1]", "Ecor[2,1]", "Rcor[2,1]", "Pcor[2,1]", "SRN_h2[1]", "SRN_h2[2]"), prob = 0.9 )
The model appears to be accurately recovering the directions and relative magnitudes of the selection coefficients in the fitness model (beta_N1 = 0.3, beta_N2 = -0.3, beta_S1 = 0.3 beta_S2 = -0.3 beta_i1 = -0.3 beta_i2 = -0.3). We can further summarize all model parameters.
summary(stan_results5, pars = c( "psi_1", "nu_0", "beta_B", "beta_N1", "beta_N2",
"beta_S1", "beta_S2", "beta_I1", "beta_I2", "V_P[1]", "V_P[2]", "Gcor[2,1]", "Ecor[2,1]", "Rcor[2,1]", "Pcor[2,1]", "SRN_h2[1]", "SRN_h2[2]"), prob = c(0.05,0.95))$summary
## mean se_mean sd 5% 95% n_eff Rhat
## psi_1 -0.443481995 0.0012442400 0.08324063 -0.57733524 -0.3058579 4475.7147 1.0002798
## nu_0 0.958824797 0.0009086336 0.06877314 0.84340655 1.0701557 5728.7523 0.9997568
## beta_B 1.335745242 0.0010406722 0.08754562 1.19719882 1.4854762 7076.8646 0.9997239
## beta_N1 0.363103401 0.0007758041 0.06593526 0.25677521 0.4719454 7223.2282 0.9995771
## beta_N2 -0.374373613 0.0009934570 0.08322067 -0.51425842 -0.2417959 7017.2068 0.9999595
## beta_S1 0.206472948 0.0006979521 0.06270416 0.10539869 0.3097887 8071.2623 0.9996396
## beta_S2 -0.280779114 0.0009797490 0.08093746 -0.41507764 -0.1517067 6824.4783 1.0003098
## beta_I1 -0.309451571 0.0008095484 0.07429362 -0.43109493 -0.1890723 8422.0407 1.0005881
## beta_I2 -0.326841748 0.0012992289 0.10423967 -0.50185096 -0.1598171 6437.1663 0.9996360
## V_P[1] 0.690835004 0.0017936972 0.07753070 0.57235394 0.8270569 1868.3110 0.9999533
## V_P[2] 0.545449567 0.0012424577 0.06999001 0.43843487 0.6680390 3173.2841 0.9999687
## Gcor[2,1] 0.009389303 0.0172081810 0.35366488 -0.62581134 0.5319919 422.3903 1.0133265
## Ecor[2,1] 0.522813019 0.0048832868 0.12863585 0.30033595 0.7202579 693.9043 1.0054932
## Rcor[2,1] -0.258956344 0.0003668427 0.03058283 -0.30832108 -0.2086652 6950.1709 1.0000481
## Pcor[2,1] 0.375065822 0.0009400725 0.06355764 0.26736238 0.4756247 4571.0172 0.9999272
## SRN_h2[1] 0.365168571 0.0069090736 0.14819786 0.13374719 0.6275880 460.0915 1.0063465
## SRN_h2[2] 0.285351378 0.0077327609 0.16022277 0.05502978 0.5790066 429.3182 1.0078258
5.3 Estimating assortment
The mean partner intrinsic trait values calculated in the model can then be used to estimate the assortment coefficient of interest \(\beta_{{\bar{\psi}'}\psi}\), as well as the broader assortment matrix \(\boldsymbol{B_{\alpha}}\). We can directly calculate the quantities of the assortment matrix using the vectors of mean partner SRN parameters that we constructed and estimated with the Stan model. In this case, we’re also interesting in estimating the expected assortment within any particular breeding season, under the assumption that variation in assortment coefficients between seasons is random. To do this, we need to scale the (co)variances of interest appropriately for the expected variance of a single partner, rather than the mean of multiple partners. If the intrinsic SRN parameter values of social partners are independent Gaussian variables, then the expected variance for a single partner phenotype \(\alpha'_k\) can be derived from the variance of the mean phenotype of \(n\) partners such that
\[\mathrm{var}\left({\alpha'_k} \right) = \mathrm{var} \left( \frac{1}{n}\Sigma^n_{k=1}\alpha_k \right)*n \] Conversely, we can derive the expected variance of the mean of \(n\) partners by dividing through the expected variance of a single partner \(\mathrm{var}\left({\alpha'_k} \right) /n\). In this simplified simulation, individuals and their social partners’ SRN parameters are characterized by the same population variances, so we can simply use the expected variance of individual SRN intercepts and slopes, i.e. \(\mathrm{var}(\boldsymbol{\alpha})=\mathrm{var}(\boldsymbol{\alpha'})\), to calculate the expected variance of the mean of \(n\) partner phenotypes. We can then use this variance to transform the vector of mean partner trait values to a standardized scale (i.e. variance = 1, z-scores), and subsequently scale these standardized values to the expected variance of a single partner using \(\mathrm{var}\left({\alpha'_k} \right)\).
#extract posteriors
<- rstan::extract(stan_results5)
post
#temporary vectors for assortment coefficients
= post$V_P
SRN_PV = post$sd_P
SRN_Psd = post$V_P / I_partner #expected variance for mean of partners
SRN_PVmean = sqrt(SRN_PVmean) #expected SD for mean of partners
SRN_Psdmean <- post$SRN_P[,,1] #individual intercepts
SRN_focal1 <- post$SRN_P[,,2] #individual slopes
SRN_focal2 <- cbind(post$partner_meanm[,,1], post$partner_meanf[,,1])
SRN_partner1 <- cbind(post$partner_meanm[,,2], post$partner_meanf[,,2])
SRN_partner2
#scale mean partner variance to variance of single partner
= SRN_partner1
SRN_partner1s for(j in 1:nrow(SRN_partner1))
= ( SRN_partner1[j,] / SRN_Psdmean[j,1] ) * SRN_Psd[j,1] }
{SRN_partner1s[j,]
= SRN_partner2
SRN_partner2s for(j in 1:nrow(SRN_partner2))
= ( SRN_partner2[j,] / SRN_Psdmean[j,2] ) * SRN_Psd[j,2] }
{SRN_partner2s[j,]
#assortment matrix
= list()
Beta_alpha
#generate matrices across each posterior sample
for(j in 1:nrow(SRN_focal1))
{= matrix(NA,2,2)
Beta_mat
#mu' ~ mu
1,1] = cov(SRN_focal1[j,], SRN_partner1s[j,])/var(SRN_focal1[j,])
Beta_mat[#mu' ~ psi
2,1] = cov(SRN_focal2[j,], SRN_partner1s[j,])/var(SRN_focal2[j,])
Beta_mat[#psi' ~ mu
1,2] = cov(SRN_focal1[j,], SRN_partner2s[j,])/var(SRN_focal1[j,])
Beta_mat[#psi' ~ psi
2,2] = cov(SRN_focal2[j,], SRN_partner2s[j,])/var(SRN_focal2[j,])
Beta_mat[
= Beta_mat
Beta_alpha[[j]]
}
#extract beta_mu'mu (assortment on SRN intercepts)
= unlist(lapply(Beta_alpha, function(x) x[2,2]))
Beta_psi median(Beta_psi); sum(Beta_psi > 0)/length(Beta_psi)
## [1] 0.2270953
## [1] 1
Positive assortment of moderate effect size is detected.
5.4 Estimating selection differentials and genetic responses
We’re now in a position to estimate the effects of selection. First, we should calculate the ‘true’ selection differential and response to gauge the degree of bias and uncertainty in our empirical estimates. Following Eq6-8, the selection differentials for SRN parameters are given by
\[\boldsymbol{s}=\begin{Bmatrix} s_{\bar{\mu}} \\ s_{{\bar{\psi}}} \end{Bmatrix} = \boldsymbol{P}\boldsymbol{\beta_N}+\boldsymbol{C}\boldsymbol{\beta_{S}}\]
where \(\boldsymbol{P}=\boldsymbol{G}+\boldsymbol{E}\) is the phenotypic covariance of individuals’ SRN parameters and \(\boldsymbol{C}\) is the covariance between individuals’ SRN parameters and the mean SRN parameters of their social partners. As noted above, we have assumed for simplicity that selection effects are constant across breeding seasons, which could be tested in an empirical dataset by including interactions between the basic SAM fitness model coefficients and a variable for the season (or selection event more generally). In this case, we use the repeated fitness measures to estimate the expected selection effects across seasons. Then using our information on the assortment among social partners within a season, we can estimate the expected selection differential within a season. In particular, we can calculate \(\boldsymbol{C}\) by
\[\mathrm{diag}(\boldsymbol{P})\boldsymbol{B_{\alpha}}\] where \(\mathrm{diag}(\boldsymbol{P})\) is a matrix with the variances of SRN parameters on the diagonal and \(\boldsymbol{B_{\alpha}}\) is the assortment matrix calculated above. With this information, as well as the simulated values fixed above, we can derive the expected selection gradient within any particular season. Note that because we’ve used zero-centered SRN parameters in the fitness model, the expected response is not influenced by the interaction coefficients for intercepts and slopes. See Appendix S1 of Martin and Jaeggi (2021) for further discussion. As noted above, this assumption can be removed from the fitness model by modifying the SAM code e.g. to use absolute (population + individual) values or season-specific covariates to account for between-season variation in the population mean.
#selection differentials and response
= G_cov + E_cov
P_cov = matrix(c(beta_n1,beta_n2),2,1)
Beta_N = matrix(c(beta_s1,beta_s2),2,1)
Beta_S = matrix(c(0,0,0,r_alpha),2,2) #lower right corner, psi'~psi
B_alpha
= P_cov %*% Beta_N + diag(diag(P_cov),2) %*% B_alpha %*% Beta_S
true_differential true_differential
## [,1]
## [1,] 0.126
## [2,] -0.180
= G_cov %*% Beta_N + diag(diag(G_cov),2) %*% B_alpha %*% Beta_S
true_response true_response
## [,1]
## [1,] 0.063
## [2,] -0.090
Now we can calculate our empirical prediction and compare.
#generate other relevant matrices
= matrix(c(post$beta_N1,post$beta_N2),ncol=2)
Beta_N = matrix(c(post$beta_S1,post$beta_S2),ncol=2)
Beta_S = post$Pcov
P = post$Gcov
G
#selection differential
#initialize dataframe
= data.frame(s_mu = rep(NA,nrow(Beta_N)), s_psi = rep(NA,nrow(Beta_N)))
s_SRN
#populate with selection differentials
for(j in 1:nrow(P)){
= P[j,,] %*% t(t(Beta_N[j,])) + diag(diag(P[j,,]),2,) %*% Beta_alpha[[j]] %*% t(t(Beta_S[j,])) }
s_SRN[j,]
#median
apply(s_SRN,2,median)
## s_mu s_psi
## 0.1590266 -0.1388207
#absolute bias for true value
apply(s_SRN,2,median) - true_differential
## [,1]
## [1,] 0.03302660
## [2,] 0.04117927
#response to selection
#initialize dataframe
= data.frame(delta_mu= rep(NA,nrow(Beta_N)), delta_psi = rep(NA,nrow(Beta_N)))
response_SRN
#populate with response to selection
for(j in 1:nrow(G)){
= G[j,,] %*% t(t(Beta_N[j,])) + diag(diag(G[j,,]),2,) %*% Beta_alpha[[j]] %*% t(t(Beta_S[j,])) }
response_SRN[j,]
apply(response_SRN,2,median)
## delta_mu delta_psi
## 0.08024632 -0.05399714
#absolute bias for true value
apply(response_SRN,2,median) - true_response
## [,1]
## [1,] 0.01724632
## [2,] 0.03600286
As shown in Figure 2 of Martin and Jaeggi (2021), we expect desirable performance at this sample size for accurately predicting the selection differentials and responses.