| Marc Kupietz | a6e4ee6 | 2021-03-05 09:00:15 +0100 | [diff] [blame] | 1 | #' Misc functions |
| 2 | #' |
| 3 | #' @name misc-functions |
| 4 | NULL |
| 5 | #' NULL |
| Marc Kupietz | bb7d232 | 2019-10-06 21:42:34 +0200 | [diff] [blame] | 6 | |
| 7 | #' Convert corpus frequency table to instances per million. |
| 8 | #' |
| 9 | #' Convenience function for converting frequency tables to instances per |
| 10 | #' million. |
| 11 | #' |
| Marc Kupietz | 67edcb5 | 2021-09-20 21:54:24 +0200 | [diff] [blame] | 12 | #' Given a table with columns `f`, `conf.low`, and `conf.high`, `ipm` ads a `column ipm` |
| 13 | #' und multiplies conf.low and `conf.high` with 10^6. |
| Marc Kupietz | bb7d232 | 2019-10-06 21:42:34 +0200 | [diff] [blame] | 14 | #' |
| Marc Kupietz | 67edcb5 | 2021-09-20 21:54:24 +0200 | [diff] [blame] | 15 | #' @param df table returned from [frequencyQuery()] |
| Marc Kupietz | bb7d232 | 2019-10-06 21:42:34 +0200 | [diff] [blame] | 16 | #' |
| Marc Kupietz | 67edcb5 | 2021-09-20 21:54:24 +0200 | [diff] [blame] | 17 | #' @return original table with additional column `ipm` and converted columns `conf.low` and `conf.high` |
| Marc Kupietz | bb7d232 | 2019-10-06 21:42:34 +0200 | [diff] [blame] | 18 | #' @export |
| 19 | #' |
| Marc Kupietz | a6e4ee6 | 2021-03-05 09:00:15 +0100 | [diff] [blame] | 20 | #' @rdname misc-functions |
| Marc Kupietz | bb7d232 | 2019-10-06 21:42:34 +0200 | [diff] [blame] | 21 | #' @importFrom dplyr .data |
| 22 | #' |
| 23 | #' @examples |
| Marc Kupietz | 6ae7605 | 2021-09-21 10:34:00 +0200 | [diff] [blame] | 24 | #' \dontrun{ |
| 25 | #' |
| Marc Kupietz | 617266d | 2025-02-27 10:43:07 +0100 | [diff] [blame] | 26 | #' KorAPConnection() %>% frequencyQuery("Test", paste0("pubDate in ", 2000:2002)) %>% ipm() |
| Marc Kupietz | 05b2277 | 2020-02-18 21:58:42 +0100 | [diff] [blame] | 27 | #' } |
| Marc Kupietz | bb7d232 | 2019-10-06 21:42:34 +0200 | [diff] [blame] | 28 | ipm <- function(df) { |
| 29 | df %>% |
| 30 | mutate(ipm = .data$f * 10^6, conf.low = .data$conf.low * 10^6, conf.high = .data$conf.high * 10^6) |
| 31 | } |
| 32 | |
| Marc Kupietz | 23daf5b | 2019-11-27 10:28:07 +0100 | [diff] [blame] | 33 | #' Convert corpus frequency table of alternatives to percent |
| 34 | #' |
| 35 | #' Convenience function for converting frequency tables of alternative variants |
| Marc Kupietz | 67edcb5 | 2021-09-20 21:54:24 +0200 | [diff] [blame] | 36 | #' (generated with `as.alternatives=TRUE`) to percent. |
| Marc Kupietz | 23daf5b | 2019-11-27 10:28:07 +0100 | [diff] [blame] | 37 | #' |
| Marc Kupietz | 67edcb5 | 2021-09-20 21:54:24 +0200 | [diff] [blame] | 38 | #' @param df table returned from [frequencyQuery()] |
| Marc Kupietz | 23daf5b | 2019-11-27 10:28:07 +0100 | [diff] [blame] | 39 | #' |
| Marc Kupietz | 67edcb5 | 2021-09-20 21:54:24 +0200 | [diff] [blame] | 40 | #' @return original table with converted columns `f`, `conf.low` and `conf.high` |
| Marc Kupietz | 23daf5b | 2019-11-27 10:28:07 +0100 | [diff] [blame] | 41 | #' @export |
| 42 | #' |
| 43 | #' @importFrom dplyr .data |
| 44 | #' |
| Marc Kupietz | a6e4ee6 | 2021-03-05 09:00:15 +0100 | [diff] [blame] | 45 | #' @rdname misc-functions |
| Marc Kupietz | 23daf5b | 2019-11-27 10:28:07 +0100 | [diff] [blame] | 46 | #' @examples |
| Marc Kupietz | 6ae7605 | 2021-09-21 10:34:00 +0200 | [diff] [blame] | 47 | #' \dontrun{ |
| 48 | #' |
| Marc Kupietz | 617266d | 2025-02-27 10:43:07 +0100 | [diff] [blame] | 49 | #' KorAPConnection() %>% |
| Marc Kupietz | 23daf5b | 2019-11-27 10:28:07 +0100 | [diff] [blame] | 50 | #' frequencyQuery(c("Tollpatsch", "Tolpatsch"), |
| 51 | #' vc=paste0("pubDate in ", 2000:2002), |
| 52 | #' as.alternatives = TRUE) %>% |
| 53 | #' percent() |
| Marc Kupietz | 05b2277 | 2020-02-18 21:58:42 +0100 | [diff] [blame] | 54 | #' } |
| Marc Kupietz | 23daf5b | 2019-11-27 10:28:07 +0100 | [diff] [blame] | 55 | percent <- function(df) { |
| 56 | df %>% |
| 57 | mutate(f = .data$f * 10^2, conf.low = .data$conf.low * 10^2, conf.high = .data$conf.high * 10^2) |
| 58 | } |
| 59 | |
| Marc Kupietz | ef5b7c1 | 2026-09-01 07:24:43 +0200 | [diff] [blame] | 60 | #' Number of characters that all given strings share as a prefix |
| 61 | #' |
| 62 | #' Replaces `PTXQC::lcpCount()`, to avoid depending on PTXQC for two small |
| 63 | #' string functions. The length is determined by binary search over vectorized |
| 64 | #' [startsWith()] calls, which needs `log2(n)` instead of `n` comparisons for a |
| 65 | #' common prefix of `n` characters. |
| 66 | #' |
| 67 | #' As in `PTXQC::lcpCount()`, a single string is its own prefix, and an empty |
| 68 | #' vector has a common prefix of length 0. |
| 69 | #' |
| 70 | #' @param strings vector of strings |
| 71 | #' @return number of characters common to the beginning of all `strings` |
| 72 | #' @noRd |
| 73 | longestCommonPrefixLength <- function(strings) { |
| 74 | strings <- as.character(strings) |
| 75 | if (length(strings) == 0) { |
| 76 | return(0L) |
| 77 | } |
| 78 | if (length(strings) == 1) { |
| 79 | return(nchar(strings[1])) |
| 80 | } |
| 81 | |
| 82 | low <- 0L |
| 83 | high <- min(nchar(strings)) |
| 84 | while (low < high) { |
| 85 | middle <- (low + high + 1L) %/% 2L |
| 86 | # isTRUE keeps NAs from breaking the condition, as in PTXQC, where they |
| 87 | # make the character comparison fail and thus end the common prefix |
| 88 | if (isTRUE(all(startsWith(strings, substr(strings[1], 1L, middle))))) { |
| 89 | low <- middle |
| 90 | } else { |
| 91 | high <- middle - 1L |
| 92 | } |
| 93 | } |
| 94 | low |
| 95 | } |
| 96 | |
| 97 | #' Number of characters that all given strings share as a suffix |
| 98 | #' |
| 99 | #' Replaces `PTXQC::lcsCount()`, which reverses every string character by |
| 100 | #' character before looking for the common prefix. Works like |
| 101 | #' `longestCommonPrefixLength()`, but anchored at the end of the strings. |
| 102 | #' |
| 103 | #' @param strings vector of strings |
| 104 | #' @return number of characters common to the end of all `strings` |
| 105 | #' @noRd |
| 106 | longestCommonSuffixLength <- function(strings) { |
| 107 | strings <- as.character(strings) |
| 108 | if (length(strings) == 0) { |
| 109 | return(0L) |
| 110 | } |
| 111 | if (length(strings) == 1) { |
| 112 | return(nchar(strings[1])) |
| 113 | } |
| 114 | |
| 115 | first <- strings[1] |
| 116 | firstLength <- nchar(first) |
| 117 | low <- 0L |
| 118 | high <- min(nchar(strings)) |
| 119 | while (low < high) { |
| 120 | middle <- (low + high + 1L) %/% 2L |
| 121 | if (isTRUE(all(endsWith(strings, substr(first, firstLength - middle + 1L, firstLength))))) { |
| 122 | low <- middle |
| 123 | } else { |
| 124 | high <- middle - 1L |
| 125 | } |
| 126 | } |
| 127 | low |
| 128 | } |
| 129 | |
| Marc Kupietz | 95240e9 | 2019-11-27 18:19:04 +0100 | [diff] [blame] | 130 | #' Convert query or vc strings to plot labels |
| 131 | #' |
| 132 | #' Converts a vector of query or vc strings to typically appropriate legend labels |
| 133 | #' by clipping off prefixes and suffixes that are common to all query strings. |
| 134 | #' |
| 135 | #' @param data string or vector of query or vc definition strings |
| Marc Kupietz | 62d29a1 | 2020-01-18 12:38:36 +0100 | [diff] [blame] | 136 | #' @param pubDateOnly discard all but the publication date |
| 137 | #' @param excludePubDate discard publication date constraints |
| Marc Kupietz | 95240e9 | 2019-11-27 18:19:04 +0100 | [diff] [blame] | 138 | #' @return string or vector of strings with clipped off common prefixes and suffixes |
| 139 | #' |
| Marc Kupietz | a6e4ee6 | 2021-03-05 09:00:15 +0100 | [diff] [blame] | 140 | #' @rdname misc-functions |
| 141 | #' |
| Marc Kupietz | 95240e9 | 2019-11-27 18:19:04 +0100 | [diff] [blame] | 142 | #' @examples |
| 143 | #' queryStringToLabel(paste("textType = /Zeit.*/ & pubDate in", c(2010:2019))) |
| 144 | #' queryStringToLabel(c("[marmot/m=mood:subj]", "[marmot/m=mood:ind]")) |
| 145 | #' queryStringToLabel(c("wegen dem [tt/p=NN]", "wegen des [tt/p=NN]")) |
| 146 | #' |
| Marc Kupietz | 95240e9 | 2019-11-27 18:19:04 +0100 | [diff] [blame] | 147 | #' @export |
| Marc Kupietz | cf1771d | 2020-03-04 16:03:04 +0100 | [diff] [blame] | 148 | queryStringToLabel <- function(data, pubDateOnly = FALSE, excludePubDate = FALSE) { |
| Marc Kupietz | 62d29a1 | 2020-01-18 12:38:36 +0100 | [diff] [blame] | 149 | if (pubDateOnly) { |
| Marc Kupietz | 7aa4f19 | 2021-03-05 10:51:24 +0100 | [diff] [blame] | 150 | data <-substring(data, regexpr("(pub|creation)Date", data)+7) |
| Marc Kupietz | 62d29a1 | 2020-01-18 12:38:36 +0100 | [diff] [blame] | 151 | } else if(excludePubDate) { |
| Marc Kupietz | 7aa4f19 | 2021-03-05 10:51:24 +0100 | [diff] [blame] | 152 | data <-substring(data, 1, regexpr("(pub|creation)Date", data)) |
| Marc Kupietz | 62d29a1 | 2020-01-18 12:38:36 +0100 | [diff] [blame] | 153 | } |
| Marc Kupietz | ef5b7c1 | 2026-09-01 07:24:43 +0200 | [diff] [blame] | 154 | leftCommon = longestCommonPrefixLength(data) |
| Marc Kupietz | 62d29a1 | 2020-01-18 12:38:36 +0100 | [diff] [blame] | 155 | while (leftCommon > 0 && grepl("[[:alnum:]/=.*!]", substring(data[1], leftCommon, leftCommon))) { |
| Marc Kupietz | 95240e9 | 2019-11-27 18:19:04 +0100 | [diff] [blame] | 156 | leftCommon <- leftCommon - 1 |
| 157 | } |
| Marc Kupietz | ef5b7c1 | 2026-09-01 07:24:43 +0200 | [diff] [blame] | 158 | rightCommon = longestCommonSuffixLength(data) |
| Marc Kupietz | 62d29a1 | 2020-01-18 12:38:36 +0100 | [diff] [blame] | 159 | while (rightCommon > 0 && grepl("[[:alnum:]/=.*!]", substring(data[1], 1+nchar(data[1]) - rightCommon, 1+nchar(data[1]) - rightCommon))) { |
| Marc Kupietz | 95240e9 | 2019-11-27 18:19:04 +0100 | [diff] [blame] | 160 | rightCommon <- rightCommon - 1 |
| 161 | } |
| 162 | substring(data, leftCommon + 1, nchar(data) - rightCommon) |
| 163 | } |
| 164 | |
| Marc Kupietz | f632fe3 | 2026-09-08 07:58:46 +0200 | [diff] [blame^] | 165 | #' Labels for a vector of virtual corpora |
| 166 | #' |
| 167 | #' The names of a named vector are what the caller chose to call their virtual |
| 168 | #' corpora, so they are what a label should say. Where a vector is named only in |
| 169 | #' part, [queryStringToLabel()] derives the rest from the definitions. |
| 170 | #' |
| 171 | #' @param vc character vector of virtual corpus definitions |
| 172 | #' @return character vector of labels, or `NULL` if `vc` carries no names at all |
| 173 | #' @noRd |
| 174 | vcLabels <- function(vc) { |
| 175 | labels <- names(vc) |
| 176 | if (is.null(labels)) { |
| 177 | return(NULL) |
| 178 | } |
| 179 | unnamed <- is.na(labels) | !nzchar(labels) |
| 180 | if (any(unnamed)) { |
| 181 | labels[unnamed] <- queryStringToLabel(vc)[unnamed] |
| 182 | } |
| 183 | unname(labels) |
| 184 | } |
| 185 | |
| 186 | #' Labels for a vector of virtual corpora, always giving one |
| 187 | #' |
| 188 | #' Like [vcLabels()], but falling back to [queryStringToLabel()] where the |
| 189 | #' vector carries no names, for callers that label unconditionally. |
| 190 | #' |
| 191 | #' @param vc character vector of virtual corpus definitions |
| 192 | #' @return character vector of labels, one per element of `vc` |
| 193 | #' @noRd |
| 194 | vcLabelsOrGuess <- function(vc) { |
| 195 | labels <- vcLabels(vc) |
| 196 | if (is.null(labels)) queryStringToLabel(vc) else labels |
| 197 | } |
| 198 | |
| Marc Kupietz | bb7d232 | 2019-10-06 21:42:34 +0200 | [diff] [blame] | 199 | |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 200 | ## Mute notes: "Undefined global functions or variables:" |
| 201 | globalVariables(c("conf.high", "conf.low", "onRender", "webUIRequestUrl")) |
| 202 | |
| 203 | |
| 204 | #' Experimental: Plot frequency by year graphs with confidence intervals |
| Marc Kupietz | d68f971 | 2019-10-06 21:48:00 +0200 | [diff] [blame] | 205 | #' |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 206 | #' Experimental convenience function for plotting typical frequency by year graphs with confidence intervals using ggplot2. |
| Marc Kupietz | 67edcb5 | 2021-09-20 21:54:24 +0200 | [diff] [blame] | 207 | #' **Warning:** This function may be moved to a new package. |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 208 | #' |
| 209 | #' @param mapping Set of aesthetic mappings created by aes() or aes_(). If specified and inherit.aes = TRUE (the default), it is combined with the default mapping at the top level of the plot. You must supply mapping if there is no plot mapping. |
| 210 | #' @param ... Other arguments passed to geom_ribbon, geom_line, and geom_click_point. |
| Marc Kupietz | d68f971 | 2019-10-06 21:48:00 +0200 | [diff] [blame] | 211 | #' |
| Marc Kupietz | a6e4ee6 | 2021-03-05 09:00:15 +0100 | [diff] [blame] | 212 | #' @rdname misc-functions |
| 213 | #' |
| Marc Kupietz | d68f971 | 2019-10-06 21:48:00 +0200 | [diff] [blame] | 214 | #' @examples |
| Marc Kupietz | 548ac35 | 2023-04-18 17:38:37 +0200 | [diff] [blame] | 215 | #' \dontrun{ |
| Marc Kupietz | d68f971 | 2019-10-06 21:48:00 +0200 | [diff] [blame] | 216 | #' library(ggplot2) |
| Marc Kupietz | 617266d | 2025-02-27 10:43:07 +0100 | [diff] [blame] | 217 | #' kco <- KorAPConnection(verbose=TRUE) |
| Marc Kupietz | 6ae7605 | 2021-09-21 10:34:00 +0200 | [diff] [blame] | 218 | #' |
| Marc Kupietz | d68f971 | 2019-10-06 21:48:00 +0200 | [diff] [blame] | 219 | #' expand_grid(condition = c("textDomain = /Wirtschaft.*/", "textDomain != /Wirtschaft.*/"), |
| Marc Kupietz | 2fbac3d | 2020-01-18 11:01:21 +0100 | [diff] [blame] | 220 | #' year = (2005:2011)) %>% |
| Marc Kupietz | d68f971 | 2019-10-06 21:48:00 +0200 | [diff] [blame] | 221 | #' cbind(frequencyQuery(kco, "[tt/l=Heuschrecke]", |
| 222 | #' paste0(.$condition," & pubDate in ", .$year))) %>% |
| 223 | #' ipm() %>% |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 224 | #' ggplot(aes(year, ipm, fill = condition, color = condition)) + |
| Marc Kupietz | d68f971 | 2019-10-06 21:48:00 +0200 | [diff] [blame] | 225 | #' geom_freq_by_year_ci() |
| Marc Kupietz | 05b2277 | 2020-02-18 21:58:42 +0100 | [diff] [blame] | 226 | #' } |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 227 | #' @importFrom ggplot2 ggplot aes geom_ribbon geom_line geom_point theme element_text scale_x_continuous |
| Marc Kupietz | d68f971 | 2019-10-06 21:48:00 +0200 | [diff] [blame] | 228 | #' |
| 229 | #' @export |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 230 | geom_freq_by_year_ci <- function(mapping = aes(ymin=conf.low, ymax=conf.high), ...) { |
| Marc Kupietz | d68f971 | 2019-10-06 21:48:00 +0200 | [diff] [blame] | 231 | list( |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 232 | geom_ribbon(mapping, |
| 233 | alpha = .3, linetype = 0, show.legend = FALSE, ...), |
| 234 | geom_line(...), |
| 235 | geom_click_point(aes(url=webUIRequestUrl), ...), |
| Marc Kupietz | 38e4e02 | 2024-04-29 17:21:48 +0200 | [diff] [blame] | 236 | theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position.inside = c(0.8, 0.2)), |
| Marc Kupietz | d68f971 | 2019-10-06 21:48:00 +0200 | [diff] [blame] | 237 | scale_x_continuous(breaks = function(x) seq(ceiling(x[1]), floor(x[2]), by = 1 + floor(((x[2]-x[1])/30))))) |
| 238 | } |
| 239 | |
| Marc Kupietz | 5fb892e | 2021-03-05 08:18:25 +0100 | [diff] [blame] | 240 | #' |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 241 | #' @importFrom ggplot2 ggproto aes GeomPoint |
| Marc Kupietz | 5fb892e | 2021-03-05 08:18:25 +0100 | [diff] [blame] | 242 | #' |
| 243 | GeomClickPoint <- ggplot2::ggproto( |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 244 | "GeomPoint", |
| Marc Kupietz | 5fb892e | 2021-03-05 08:18:25 +0100 | [diff] [blame] | 245 | ggplot2::GeomPoint, |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 246 | required_aes = c("x", "y"), |
| 247 | default_aes = aes( |
| 248 | shape = 19, colour = "black", size = 1.5, fill = NA, |
| 249 | alpha = NA, stroke = 0.5, url = NA |
| 250 | ), |
| 251 | extra_params = c("na.rm", "url"), |
| 252 | draw_panel = function(data, panel_params, |
| 253 | coord, na.rm = FALSE, showpoints = TRUE, url = NULL) { |
| 254 | GeomPoint$draw_panel(data, panel_params, coord, na.rm = na.rm) |
| 255 | } |
| 256 | ) |
| 257 | |
| 258 | #' @importFrom ggplot2 layer |
| 259 | geom_click_point <- function(mapping = NULL, data = NULL, stat = "identity", |
| Marc Kupietz | 5fb892e | 2021-03-05 08:18:25 +0100 | [diff] [blame] | 260 | position = "identity", na.rm = FALSE, show.legend = NA, |
| 261 | inherit.aes = TRUE, url = NA, ...) { |
| Marc Kupietz | 865760f | 2019-10-07 19:29:44 +0200 | [diff] [blame] | 262 | layer( |
| 263 | geom = GeomClickPoint, mapping = mapping, data = data, stat = stat, |
| 264 | position = position, show.legend = show.legend, inherit.aes = inherit.aes, |
| 265 | params = list(na.rm = na.rm, ...) |
| 266 | ) |
| 267 | } |
| 268 | |