blob: 2294bdf78b307146042d0ec7e1fa211220f4d865 [file] [log] [blame]
Marc Kupietza6e4ee62021-03-05 09:00:15 +01001#' Misc functions
2#'
3#' @name misc-functions
4NULL
5#' NULL
Marc Kupietzbb7d2322019-10-06 21:42:34 +02006
7#' Convert corpus frequency table to instances per million.
8#'
9#' Convenience function for converting frequency tables to instances per
10#' million.
11#'
Marc Kupietz67edcb52021-09-20 21:54:24 +020012#' 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 Kupietzbb7d2322019-10-06 21:42:34 +020014#'
Marc Kupietz67edcb52021-09-20 21:54:24 +020015#' @param df table returned from [frequencyQuery()]
Marc Kupietzbb7d2322019-10-06 21:42:34 +020016#'
Marc Kupietz67edcb52021-09-20 21:54:24 +020017#' @return original table with additional column `ipm` and converted columns `conf.low` and `conf.high`
Marc Kupietzbb7d2322019-10-06 21:42:34 +020018#' @export
19#'
Marc Kupietza6e4ee62021-03-05 09:00:15 +010020#' @rdname misc-functions
Marc Kupietzbb7d2322019-10-06 21:42:34 +020021#' @importFrom dplyr .data
22#'
23#' @examples
Marc Kupietz6ae76052021-09-21 10:34:00 +020024#' \dontrun{
25#'
Marc Kupietz617266d2025-02-27 10:43:07 +010026#' KorAPConnection() %>% frequencyQuery("Test", paste0("pubDate in ", 2000:2002)) %>% ipm()
Marc Kupietz05b22772020-02-18 21:58:42 +010027#' }
Marc Kupietzbb7d2322019-10-06 21:42:34 +020028ipm <- 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 Kupietz23daf5b2019-11-27 10:28:07 +010033#' Convert corpus frequency table of alternatives to percent
34#'
35#' Convenience function for converting frequency tables of alternative variants
Marc Kupietz67edcb52021-09-20 21:54:24 +020036#' (generated with `as.alternatives=TRUE`) to percent.
Marc Kupietz23daf5b2019-11-27 10:28:07 +010037#'
Marc Kupietz67edcb52021-09-20 21:54:24 +020038#' @param df table returned from [frequencyQuery()]
Marc Kupietz23daf5b2019-11-27 10:28:07 +010039#'
Marc Kupietz67edcb52021-09-20 21:54:24 +020040#' @return original table with converted columns `f`, `conf.low` and `conf.high`
Marc Kupietz23daf5b2019-11-27 10:28:07 +010041#' @export
42#'
43#' @importFrom dplyr .data
44#'
Marc Kupietza6e4ee62021-03-05 09:00:15 +010045#' @rdname misc-functions
Marc Kupietz23daf5b2019-11-27 10:28:07 +010046#' @examples
Marc Kupietz6ae76052021-09-21 10:34:00 +020047#' \dontrun{
48#'
Marc Kupietz617266d2025-02-27 10:43:07 +010049#' KorAPConnection() %>%
Marc Kupietz23daf5b2019-11-27 10:28:07 +010050#' frequencyQuery(c("Tollpatsch", "Tolpatsch"),
51#' vc=paste0("pubDate in ", 2000:2002),
52#' as.alternatives = TRUE) %>%
53#' percent()
Marc Kupietz05b22772020-02-18 21:58:42 +010054#' }
Marc Kupietz23daf5b2019-11-27 10:28:07 +010055percent <- 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 Kupietzef5b7c12026-09-01 07:24:43 +020060#' 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
73longestCommonPrefixLength <- 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
106longestCommonSuffixLength <- 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 Kupietz95240e92019-11-27 18:19:04 +0100130#' 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 Kupietz62d29a12020-01-18 12:38:36 +0100136#' @param pubDateOnly discard all but the publication date
137#' @param excludePubDate discard publication date constraints
Marc Kupietz95240e92019-11-27 18:19:04 +0100138#' @return string or vector of strings with clipped off common prefixes and suffixes
139#'
Marc Kupietza6e4ee62021-03-05 09:00:15 +0100140#' @rdname misc-functions
141#'
Marc Kupietz95240e92019-11-27 18:19:04 +0100142#' @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 Kupietz95240e92019-11-27 18:19:04 +0100147#' @export
Marc Kupietzcf1771d2020-03-04 16:03:04 +0100148queryStringToLabel <- function(data, pubDateOnly = FALSE, excludePubDate = FALSE) {
Marc Kupietz62d29a12020-01-18 12:38:36 +0100149 if (pubDateOnly) {
Marc Kupietz7aa4f192021-03-05 10:51:24 +0100150 data <-substring(data, regexpr("(pub|creation)Date", data)+7)
Marc Kupietz62d29a12020-01-18 12:38:36 +0100151 } else if(excludePubDate) {
Marc Kupietz7aa4f192021-03-05 10:51:24 +0100152 data <-substring(data, 1, regexpr("(pub|creation)Date", data))
Marc Kupietz62d29a12020-01-18 12:38:36 +0100153 }
Marc Kupietzef5b7c12026-09-01 07:24:43 +0200154 leftCommon = longestCommonPrefixLength(data)
Marc Kupietz62d29a12020-01-18 12:38:36 +0100155 while (leftCommon > 0 && grepl("[[:alnum:]/=.*!]", substring(data[1], leftCommon, leftCommon))) {
Marc Kupietz95240e92019-11-27 18:19:04 +0100156 leftCommon <- leftCommon - 1
157 }
Marc Kupietzef5b7c12026-09-01 07:24:43 +0200158 rightCommon = longestCommonSuffixLength(data)
Marc Kupietz62d29a12020-01-18 12:38:36 +0100159 while (rightCommon > 0 && grepl("[[:alnum:]/=.*!]", substring(data[1], 1+nchar(data[1]) - rightCommon, 1+nchar(data[1]) - rightCommon))) {
Marc Kupietz95240e92019-11-27 18:19:04 +0100160 rightCommon <- rightCommon - 1
161 }
162 substring(data, leftCommon + 1, nchar(data) - rightCommon)
163}
164
Marc Kupietzf632fe32026-09-08 07:58:46 +0200165#' 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
174vcLabels <- 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#'
Marc Kupietz437d7af2026-09-09 14:40:02 +0200188#' Like `vcLabels()`, but falling back to [queryStringToLabel()] where the
Marc Kupietzf632fe32026-09-08 07:58:46 +0200189#' 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
194vcLabelsOrGuess <- function(vc) {
195 labels <- vcLabels(vc)
196 if (is.null(labels)) queryStringToLabel(vc) else labels
197}
198
Marc Kupietzbb7d2322019-10-06 21:42:34 +0200199
Marc Kupietz865760f2019-10-07 19:29:44 +0200200## Mute notes: "Undefined global functions or variables:"
201globalVariables(c("conf.high", "conf.low", "onRender", "webUIRequestUrl"))
202
203
204#' Experimental: Plot frequency by year graphs with confidence intervals
Marc Kupietzd68f9712019-10-06 21:48:00 +0200205#'
Marc Kupietz865760f2019-10-07 19:29:44 +0200206#' Experimental convenience function for plotting typical frequency by year graphs with confidence intervals using ggplot2.
Marc Kupietz67edcb52021-09-20 21:54:24 +0200207#' **Warning:** This function may be moved to a new package.
Marc Kupietz865760f2019-10-07 19:29:44 +0200208#'
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 Kupietzd68f9712019-10-06 21:48:00 +0200211#'
Marc Kupietza6e4ee62021-03-05 09:00:15 +0100212#' @rdname misc-functions
213#'
Marc Kupietzd68f9712019-10-06 21:48:00 +0200214#' @examples
Marc Kupietz548ac352023-04-18 17:38:37 +0200215#' \dontrun{
Marc Kupietzd68f9712019-10-06 21:48:00 +0200216#' library(ggplot2)
Marc Kupietz617266d2025-02-27 10:43:07 +0100217#' kco <- KorAPConnection(verbose=TRUE)
Marc Kupietz6ae76052021-09-21 10:34:00 +0200218#'
Marc Kupietzd68f9712019-10-06 21:48:00 +0200219#' expand_grid(condition = c("textDomain = /Wirtschaft.*/", "textDomain != /Wirtschaft.*/"),
Marc Kupietz2fbac3d2020-01-18 11:01:21 +0100220#' year = (2005:2011)) %>%
Marc Kupietzd68f9712019-10-06 21:48:00 +0200221#' cbind(frequencyQuery(kco, "[tt/l=Heuschrecke]",
222#' paste0(.$condition," & pubDate in ", .$year))) %>%
223#' ipm() %>%
Marc Kupietz865760f2019-10-07 19:29:44 +0200224#' ggplot(aes(year, ipm, fill = condition, color = condition)) +
Marc Kupietzd68f9712019-10-06 21:48:00 +0200225#' geom_freq_by_year_ci()
Marc Kupietz05b22772020-02-18 21:58:42 +0100226#' }
Marc Kupietz865760f2019-10-07 19:29:44 +0200227#' @importFrom ggplot2 ggplot aes geom_ribbon geom_line geom_point theme element_text scale_x_continuous
Marc Kupietzd68f9712019-10-06 21:48:00 +0200228#'
229#' @export
Marc Kupietz865760f2019-10-07 19:29:44 +0200230geom_freq_by_year_ci <- function(mapping = aes(ymin=conf.low, ymax=conf.high), ...) {
Marc Kupietzd68f9712019-10-06 21:48:00 +0200231 list(
Marc Kupietz865760f2019-10-07 19:29:44 +0200232 geom_ribbon(mapping,
233 alpha = .3, linetype = 0, show.legend = FALSE, ...),
234 geom_line(...),
235 geom_click_point(aes(url=webUIRequestUrl), ...),
Marc Kupietz38e4e022024-04-29 17:21:48 +0200236 theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position.inside = c(0.8, 0.2)),
Marc Kupietzd68f9712019-10-06 21:48:00 +0200237 scale_x_continuous(breaks = function(x) seq(ceiling(x[1]), floor(x[2]), by = 1 + floor(((x[2]-x[1])/30)))))
238}
239
Marc Kupietz5fb892e2021-03-05 08:18:25 +0100240#'
Marc Kupietz865760f2019-10-07 19:29:44 +0200241#' @importFrom ggplot2 ggproto aes GeomPoint
Marc Kupietz5fb892e2021-03-05 08:18:25 +0100242#'
243GeomClickPoint <- ggplot2::ggproto(
Marc Kupietz865760f2019-10-07 19:29:44 +0200244 "GeomPoint",
Marc Kupietz5fb892e2021-03-05 08:18:25 +0100245 ggplot2::GeomPoint,
Marc Kupietz865760f2019-10-07 19:29:44 +0200246 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
259geom_click_point <- function(mapping = NULL, data = NULL, stat = "identity",
Marc Kupietz5fb892e2021-03-05 08:18:25 +0100260 position = "identity", na.rm = FALSE, show.legend = NA,
261 inherit.aes = TRUE, url = NA, ...) {
Marc Kupietz865760f2019-10-07 19:29:44 +0200262 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