/
distinctiveness.R
325 lines (284 loc) · 11.2 KB
/
distinctiveness.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# Functions to compute functional distinctiveness on various datasets
#
# Authors: Pierre Denelle & Matthias Grenié
#
#
#' Functional Distinctiveness for a single community
#'
#' Given a stacked data.frame and a distance matrix compute the functional
#' distinctiveness for a single community. Functional distinctiveness relates to
#' the functional "originality" of a species in a community. The closer to 1 the
#' more the species is functionally distinct from the rest of the community. See
#' [distinctiveness()] function or the functional rarity
#' indices vignette included in the package
#' (type `vignette("rarity_indices", package = "funrar")`), for more details
#' on the metric. **IMPORTANT NOTE**: in order to get functional rarity indices
#' between 0 and 1, the distance metric has to be scaled between 0 and 1.
#'
#' @inheritParams scarcity_com
#'
#' @param dist_matrix a functional distance matrix as given by
#' `compute_dist_matrix()`, with species name as row and column names
#'
#' @param relative a logical indicating if distinctiveness should be scaled
#' relatively to the community (scaled by max functional
#' distance among the species of the targeted community)
#'
#' @return the same data.frame with the additional **Di** column giving
#' functional distinctiveness values for each species
#'
#' @section Caution:
#' This function is meant for internal uses mostly, thus it does not include any
#' tests on inputs and may fail unexpectedly. Please use
#' [distinctiveness_stack()] to avoid input errors.
#'
#' @seealso [scarcity_com()],
#' `vignette("rarity_indices", package = "funrar")` and
#' [distinctiveness()] Details section for detail on the index
#'
#' @export
distinctiveness_com = function(com_df, sp_col, abund = NULL, dist_matrix,
relative = FALSE) {
# Test relative argument
if (!is.logical(relative) || is.na(relative) || length(relative) != 1) {
stop("'relative' argument should be either TRUE or FALSE")
}
# Find species in common between community df and distance matrix
common = species_in_common_df(com_df, sp_col, dist_matrix)
com_df = subset(com_df, com_df[[sp_col]] %in% common)
# Get functional distance matrix of species in communities
com_dist = dist_matrix[common, common]
# Maxmimum functional distance among the community
max_dist = 1
if (relative) {
max_dist = max(com_dist)
}
if (!is.null(dim(com_dist))) {
if (is.null(abund)) {
# Sum the distances by species
num = colSums(com_dist)
# Number of species minus the focal species
denom = nrow(com_df) - 1
} else {
# For each species multiplies its functional distance with corresponding
# abundance to compute distinctiveness
num = apply(com_dist, 2, function(x) sum(x * com_df[, abund]))
# Compute the sum of all abundances minus the one focal species
denom = sum(com_df[[abund]]) - com_df[[abund]]
}
} else {
denom = 0
}
# Computes distinctiveness by species
if (length(denom) > 1) {
com_df[, "Di"] = as.numeric(num / denom) / max_dist
} else if (length(denom) == 1 && denom != 0) {
com_df[, "Di"] = as.numeric(num / denom) / max_dist
} else {
com_df[, "Di"] = NaN
}
# Replaces computed Di values by NA for species absent from communities
if (!is.null(abund)) {
com_df[com_df[[abund]] == 0, "Di"] = NA
}
return(com_df)
}
#' Functional Distinctiveness on a stacked data.frame
#'
#' Compute Functional Distinctiveness for several communities, from a stacked
#' (or tidy) data.frame of communities, with one column for species identity,
#' one for community identity and an optional one for relative abundances. Also
#' needs a species functional distances matrix. Functional distinctiveness
#' relates to the functional "originality" of a species in a community. The
#' closer to 1 the more the species is functionally distinct from the rest of
#' the community. See [distinctiveness()] function or the
#' functional rarity indices vignette included in the package
#' (type `vignette("rarity_indices", package = "funrar")`), for more details
#' on the metric. **IMPORTANT NOTE**: in order to get functional rarity indices
#' between 0 and 1, the distance metric has to be scaled between 0 and 1.
#' You can either use `_stack()` or `_tidy()` functions as they are aliases of
#' one another.
#'
#' @inheritParams distinctiveness_com
#'
#' @param com a character vector, the column name for communities names
#'
#' @return the same data.frame with the additional **Di** column giving
#' functional distinctiveness values for each species
#'
#' @seealso [scarcity_stack()],
#' [uniqueness_stack()],
#' [restrictedness_stack()];
#' [distinctiveness()] Details section for detail on the index
#'
#' @examples
#' data("aravo", package = "ade4")
#'
#' # Example of trait table
#' tra = aravo$traits[, c("Height", "SLA", "N_mass")]
#' # Distance matrix
#' dist_mat = compute_dist_matrix(tra)
#'
#' # Site-species matrix converted into data.frame
#' mat = as.matrix(aravo$spe)
#' mat = make_relative(mat)
#' dat = matrix_to_stack(mat, "value", "site", "species")
#' dat$site = as.character(dat$site)
#' dat$species = as.character(dat$species)
#'
#' di_df = distinctiveness_stack(dat, "species", "site", "value", dist_mat)
#' head(di_df)
#'
#' @export
distinctiveness_stack = function(com_df, sp_col, com, abund = NULL,
dist_matrix, relative = FALSE) {
# Test to be sure of inputs
full_df_checks(com_df, sp_col, com, abund, dist_matrix)
if (is.null(abund)) {
message("No abundance column provided, computing Di without it")
}
# Take subsets of species if needed between distance matrix and community
common = species_in_common_df(com_df, sp_col, dist_matrix)
com_df = subset(com_df, com_df[[sp_col]] %in% common)
dist_matrix = dist_matrix[common, common]
# Compute Distinctivenness
# Split table by communities
com_split = split(com_df, factor(com_df[[com]]))
com_split = lapply(
com_split,
function(one_com) {
distinctiveness_com(
one_com, sp_col, abund, dist_matrix, relative = relative
)
}
)
com_distinctiveness = do.call(
rbind.data.frame,
c(com_split, make.row.names = FALSE, stringsAsFactors = FALSE)
)
if(
any(vapply(com_distinctiveness[["Di"]], function(x) is.nan(x), logical(1)))
) {
warning("Some communities had a single species in them\n",
"Computed value assigned to 'NaN'")
}
return(com_distinctiveness)
}
# Distinctiveness tidy alias
#' @export
#' @rdname distinctiveness_stack
distinctiveness_tidy = distinctiveness_stack
#' Functional Distinctiveness on site-species matrix
#'
#' Computes functional distinctiveness from a site-species matrix (containing
#' presence-absence or relative abundances) of species with provided functional
#' distance matrix. The sites-species matrix should have **sites** in
#' **rows** and **species** in **columns**, similar to
#' \pkg{vegan} package defaults.
#'
#' @param pres_matrix a site-species matrix (presence-absence or relative
#' abundances), with sites in rows and species in columns
#'
#' @param dist_matrix a species functional distance matrix
#'
#' @param relative a logical indicating if distinctiveness should be scaled
#' relatively to the community (scaled by max functional
#' distance among the species of the targeted community)
#'
#' @return a similar matrix from provided `pres_matrix` with Distinctiveness
#' values in lieu of presences or relative abundances, species absent from
#' communities will have an `NA` value (see `Note` section)
#'
#' @section Note:
#' Absent species should be coded by `0` or `NA` in input matrices.
#'
#' When a species is alone in its community the functional distinctiveness
#' cannot be computed (denominator = 0 in formula), and its value is assigned
#' as `NaN`.
#'
#' For speed and memory efficiency sparse matrices can be used as input of
#' the function using `as(pres_matrix, "dgCMatrix")` from the
#' `Matrix` package.
#' (see `vignette("sparse_matrices", package = "funrar")`)
#'
#' @details
#' The Functional Distinctiveness of a species is the average functional
#' distance from a species to all the other in the given community. It is
#' computed as such:
#' \deqn{
#' D_i = \frac{\sum_{j = 0, i \neq j}^N d_{ij}}{N-1},
#' }{%
#' D_i = ( \Sigma_(j = 0, i != j)^N d_ij) / (N - 1),
#' }
#' with \eqn{D_i} the functional distinctiveness of species \eqn{i}, \eqn{N}
#' the total number of species in the community and \eqn{d_{ij}}{d_ij} the
#' functional distance between species \eqn{i} and species \eqn{j}.
#' **IMPORTANT NOTE**: in order to get functional rarity indices between 0
#' and 1, the distance metric has to be scaled between 0 and 1.
#'
#' @importFrom methods is
#'
#' @examples
#' data("aravo", package = "ade4")
#' # Site-species matrix
#' mat = as.matrix(aravo$spe)
#'
#' # Compute relative abundances
#' mat = make_relative(mat)
#'
#' # Example of trait table
#' tra = aravo$traits[, c("Height", "SLA", "N_mass")]
#' # Distance matrix
#' dist_mat = compute_dist_matrix(tra)
#'
#' di = distinctiveness(pres_matrix = mat, dist_matrix = dist_mat)
#' di[1:5, 1:5]
#'
#' # Compute distinctiveness for all species in the regional pool
#' # i.e., with all the species in all the communities
#' # Here considering each species present evenly in the regional pool
#' reg_pool = matrix(1, ncol = ncol(mat))
#' colnames(reg_pool) = colnames(mat)
#' row.names(reg_pool) = c("Regional_pool")
#'
#' reg_di = distinctiveness(reg_pool, dist_mat)
#'
#' @export
#' @import Matrix
distinctiveness = function(pres_matrix, dist_matrix, relative = FALSE) {
full_matrix_checks(pres_matrix, dist_matrix)
# Test relative argument
if (!is.logical(relative) || is.na(relative) || length(relative) != 1) {
stop("'relative' argument should be either TRUE or FALSE")
}
common = species_in_common(pres_matrix, dist_matrix)
pres_matrix = pres_matrix[, common, drop = FALSE]
dist_matrix = dist_matrix[common, common]
# Matrix product of distance matrix and presence absence matrix
index_matrix = pres_matrix %*% dist_matrix
# Replace species not present in communities
index_matrix[which(pres_matrix == 0)] = NA
total_sites = rowSums(pres_matrix)
# Subtract focal species value to site total
# /!\ need to Transpose because applying function to row tranposes matrix
denom_matrix = apply(pres_matrix, 2, function(x) total_sites - x)
# Define maximum functional distance per site to standardize
max_dist = 1
if (relative) {
max_dist = apply(pres_matrix, 1, function(row, d_mat = dist_matrix) {
non_null_sp = names(row[row != 0]) # Select present species in community
# Community distance matrix
non_null_dist = d_mat[non_null_sp, non_null_sp]
# Maximum functional distance
max(non_null_dist)
})
}
index_matrix = (index_matrix / denom_matrix) / max_dist
# Test if there is NaN in the table for species alone in their community
if (any(vapply(index_matrix, function(x) is.nan(x), logical(1)))) {
warning("Some communities had a single species in them\n",
"Computed value assigned to 'NaN'")
}
dimnames(index_matrix) = dimnames(pres_matrix)
return(index_matrix)
}