Closed cynthiahqy closed 1 year ago
Using NULL
as defaults in modify_xmap_df(xmap, from = NULL, to = NULL, weights = NULL)
doesn't really work because the user arguments are expected to be expressions not strings.
The logic I want is:
col_from <- ifelse(is.null(from),
x_attrs$col_from,
deparse(substitute(from)))
but if from
is an expression (e.g. from = ISO3
), then you get the error:
Error in ifelse(!is.null(from), x_attrs$col_from, deparse(substitute(from))) :
object 'ISO3' not found
For reference the function was:
modify_xmap_df <- function(xmap_df, from, to, weights){
stopifnot(inherits(xmap_df, "xmap_df"))
x_attrs <- attributes(xmap_df)
col_from <- ifelse(is.null(from),
x_attrs$col_from,
deparse(substitute(from)))
col_to <- ifelse(is.null(to),
x_attrs$col_to,
deparse(substitute(to)))
col_weights <- ifelse(is.null(weights),
x_attrs$col_weights,
deparse(substitute(weights)))
}
The %||%
infix also doesn't work since:
col_from <- from %||% x_attrs$col_from
will error if from = ISO3
col_from <- deparse(substitute(from)) %||% x_attrs$col_from
will never access the second value since NULL
will be defused into "NULL"These rlang
missing functions might help:
https://rlang.r-lib.org/reference/missing_arg.html
Or sentinel value:
The user needs to provide at least ONE of from
, to
, weights
, with the unspecified arguments assumed to stay the same. There should be some warning like:
if (all(missing(from), missing(to), missing(weights))){
stop("Please supply at least one of `from`, `to` or `weights` to modify `xmap_df`")
}
I think I need some type of sentinel function and to specify the default as a function call:
modify_xmap_df <- function(xmap_df,
from = attr(xmap_df, "col_from"),
to = attr(xmap_df, "col_to"),
weights = attr(xmap_df, "col_weights"))
Or something like modify_xmap_df(iso_xmap, from = xmap::same(), ...)
Not modifying the underlying data.frame (just "activate" another xmap):
reset
/switch
for switching col_
attributes with other columns already in the xmap_df
## reset (accept expressions for column names; with optional args)
xmap_reset(xmap, from, to, weights)
xmap_reset(xmap, weights = <other-weights>)
Modifies the underlying data.frame:
replace
for replacing one or more col_*
attributes (with new vectors)? BUT how do you deal with column names??? -- do you append the new values OR just replace the old ones in the existing col_*
?reweight
for replacing weighting schemes, but keeping col_from
& col_to
the same.redirect
for changing from/to attributes, but keeping the same col_weights
? reverse
for swapping from/to and generating new weights.If the modification interface accepted anonymous vectors:
## replace (with new vectors, checking vector length)
xmap_replace(xmap, v_from, v_to, v_weights)
## reweight (accept vector of new weights to replace old weights?)
xmap_reweight(xmap, v_weights)
xmap_reweight(iso_xmap, iso_xmap$other_col)
## redirect (accept strings for names of new to/from)
## @param v_weights vector of new weights, defaults to `rep(1, nrow(xmap))`
xmap_redirect(xmap, col_from, col_to, v_weights = NULL, weights_to = "r_weights")
## reverse
xmap_reverse(xmap)
# which wraps
xmap_redirect(xmap,
col_from = x_attrs$col_to,
col_to = x_attrs$col_from,
v_weights = rep(1, nrow(xmap)),
weights_to = "r_weights"
)
Alternatively, require user provided vectors to first be added to the xmap via mutate
or other table manipulation functions. There would be no replace
function and the other modifications could be recast as:
## reweight
my_xmap |>
mutate(new_weights = <vector-of-weights>) |>
xmap_reset(weights = new_weights)
which always succeeds if the new_weights
are valid for the from
-to
links.
## redirect*
my_xmap |>
# ATTEMPT: different from, same to & weights
xmap_reset(xmap, from = <other_source>)
my_xmap |>
# ATTEMPT: different from & to but SAME weights
xmap_reset(xmap, from = <other_source>, to = <other_target>)
which just throws an ERROR if the existing weights don't work
## reverse
my_xmap |>
mutate(r_weights = rep(1, n())) |>
xmap_reset(xmap, from = <col_to>, to = <col_from>, weights = r_weights)
The tricky thing is that these functions try to do a few related things:
xmap_df
-- i.e. switching out weighting schemes, no new columns!xmap_df
-- e.g. reversing keeps the from
and to
columns, but usually requires adding new weights. The creation of a related crossmap could is a bit of a multi-stage process with conditional steps:
Maybe the user should be advised to add new weights to the existing xmap
as a column, instead of the function accepting anonymous vectors of values into v_*
args.
There needs to be a weights_to
argument to provide a column name. But internally, the function adds a new column, then activates a new crossmap with that new column.
Robs suggests only offering:
modify_to()
modify_from()
modify_weights()
reverse()
and force the user to make new xmap if it's any only combination, or can't be done sequentially.Alt. name convention: xmap_switch_*
:
to
from
weights
direction
Sentinel value for arguments: from=".replace"
or similar
modify_xmap_df()
modify_xmap_df <- function(xmap_df, col_from = NULL, col_to = NULL, col_weights = NULL){
stopifnot(inherits(xmap_df, "xmap_df"))
x_attrs <- attributes(xmap_df)
col_from <- col_from %||% x_attrs$col_from
col_to <- col_to %||% x_attrs$col_to
col_weights <- col_weights %||% x_attrs$col_weights
col_strings <- c(col_from, col_to, col_weights)
df_check_cols(xmap_df, col_strings)
df <- as.data.frame(xmap_df)
col_order <- c(col_strings, setdiff(names(df), col_strings))
df <- df[col_order]
xmap <- new_xmap_df(df, col_from, col_to, col_weights)
validate_xmap_df(xmap)
return(xmap)
}
iso_xmap <- tibble::tribble(
~ISONumeric, ~ISO2, ~link, ~country, ~ISO3,
"004", "AF", 1, "Afghanistan", "AFG",
"008", "AL", 1, "Albania", "ALB",
"012", "DZ", 1, "Algeria", "DZA",
"016", "AS", 1, "American Samoa", "ASM",
"020", "AD", 1, "Andorra", "AND"
) |>
as_xmap_df(from = ISO2, to = ISONumeric, weights = link)
xmap_modify()
I'm not sure offering helpers for modifying crossmaps is necessary or even desirable.
xmap |> dplyr::mutate()
should probably return a tibble rather than an xmap.Closing this issue since modification should be reserved for candidate links; and with #82 validate_as_xmap()
and xmap_to_*()
features this is relatively straightforward.
Modifying Crossmaps
Maybe it might also make sense to provide a "modification" function like
xmap_modify(xmap, from = NULL, to = NULL, weights = NULL)
which:from
,to
&weights
arguments. i.e.xmap_modify(list(col_from = "iso2", col_to = "iso3"))
is equivalent toxmap_modify(col_from = "iso2", col_to = "iso3")
col_from
,col_to
,col_weights
as specified, leaving any unspecified attributes unchanged.It would also make for more expressive code when working with existing xmap objects -- e.g. with the
iso_xmap
object in themaking-xmaps.Rmd
vignette:https://github.com/cynthiahqy/conformr-project/blob/3d9c2843d4ed63f106ed8dd34f1ac5643b7a0b00/xmap/vignettes/making-xmaps.Rmd#L299-L317
Instead coercing an existing xmap "again", you could have something like:
This could also power the inversion function which would just be a validated wrapper around something like: