Is there a dictionary functionality in R - r

Is there a way to create a "dictionary" in R, such that it has pairs?
Something to the effect of:
x=dictionary(c("Hi","Why","water") , c(1,5,4))
x["Why"]=5
I'm asking this because I am actually looking for a two categorial variables function.
So that if x=dictionary(c("a","b"),c(5,2))
x val
1 a 5
2 b 2
I want to compute x1^2+x2 on all combinations of x keys
x1 x2 val1 val2 x1^2+x2
1 a a 5 5 30
2 b a 2 5 9
3 a b 5 2 27
4 b b 2 2 6
And then I want to be able to retrieve the result using x1 and x2. Something to the effect of:
get_result["b","a"] = 9
what is the best, efficient way to do this?

I know three R packages for dictionaries: hash, hashmap, and dict.
Update July 2018: a new one, container.
Update September 2018: a new one, collections
hash
Keys must be character strings. A value can be any R object.
library(hash)
## hash-2.2.6 provided by Decision Patterns
h <- hash()
# set values
h[["1"]] <- 42
h[["foo"]] <- "bar"
h[["4"]] <- list(a=1, b=2)
# get values
h[["1"]]
## [1] 42
h[["4"]]
## $a
## [1] 1
##
## $b
## [1] 2
h[c("1", "foo")]
## <hash> containing 2 key-value pair(s).
## 1 : 42
## foo : bar
h[["key not here"]]
## NULL
To get keys:
keys(h)
## [1] "1" "4" "foo"
To get values:
values(h)
## $`1`
## [1] 42
##
## $`4`
## $`4`$a
## [1] 1
##
## $`4`$b
## [1] 2
##
##
## $foo
## [1] "bar"
The print instance:
h
## <hash> containing 3 key-value pair(s).
## 1 : 42
## 4 : 1 2
## foo : bar
The values function accepts the arguments of sapply:
values(h, USE.NAMES=FALSE)
## [[1]]
## [1] 42
##
## [[2]]
## [[2]]$a
## [1] 1
##
## [[2]]$b
## [1] 2
##
##
## [[3]]
## [1] "bar"
values(h, keys="4")
## 4
## a 1
## b 2
values(h, keys="4", simplify=FALSE)
## $`4`
## $`4`$a
## [1] 1
##
## $`4`$b
## [1] 2
hashmap
See https://cran.r-project.org/web/packages/hashmap/README.html.
hashmap does not offer the flexibility to store arbitrary types of objects.
Keys and values are restricted to "scalar" objects (length-one character, numeric, etc.). The values must be of the same type.
library(hashmap)
H <- hashmap(c("a", "b"), rnorm(2))
H[["a"]]
## [1] 0.1549271
H[[c("a","b")]]
## [1] 0.1549271 -0.1222048
H[[1]] <- 9
Beautiful print instance:
H
## ## (character) => (numeric)
## ## [1] => [+9.000000]
## ## [b] => [-0.122205]
## ## [a] => [+0.154927]
Errors:
H[[2]] <- "Z"
## Error in x$`[[<-`(i, value): Not compatible with requested type: [type=character; target=double].
H[[2]] <- c(1,3)
## Warning in x$`[[<-`(i, value): length(keys) != length(values)!
dict
Currently available only on Github: https://github.com/mkuhn/dict
Strengths: arbitrary keys and values, and fast.
library(dict)
d <- dict()
d[[1]] <- 42
d[[c(2, 3)]] <- "Hello!" # c(2,3) is the key
d[["foo"]] <- "bar"
d[[4]] <- list(a=1, b=2)
d[[1]]
## [1] 42
d[[c(2, 3)]]
## [1] "Hello!"
d[[4]]
## $a
## [1] 1
##
## $b
## [1] 2
Accessing to a non-existing key throws an error:
d[["not here"]]
## Error in d$get_or_stop(key): Key error: [1] "not here"
But there is a nice feature to deal with that:
d$get("not here", "default value for missing key")
## [1] "default value for missing key"
Get keys:
d$keys()
## [[1]]
## [1] 4
##
## [[2]]
## [1] 1
##
## [[3]]
## [1] 2 3
##
## [[4]]
## [1] "foo"
Get values:
d$values()
## [[1]]
## [1] 42
##
## [[2]]
## [1] "Hello!"
##
## [[3]]
## [1] "bar"
##
## [[4]]
## [[4]]$a
## [1] 1
##
## [[4]]$b
## [1] 2
Get items:
d$items()
## [[1]]
## [[1]]$key
## [1] 4
##
## [[1]]$value
## [[1]]$value$a
## [1] 1
##
## [[1]]$value$b
## [1] 2
##
##
##
## [[2]]
## [[2]]$key
## [1] 1
##
## [[2]]$value
## [1] 42
##
##
## [[3]]
## [[3]]$key
## [1] 2 3
##
## [[3]]$value
## [1] "Hello!"
##
##
## [[4]]
## [[4]]$key
## [1] "foo"
##
## [[4]]$value
## [1] "bar"
No print instance.
The package also provides the function numvecdict to deal with a dictionary in which numbers and strings (including vectors of each) can be used as keys, and that can only store vectors of numbers.

You simply create a vector with your key value pairs.
animal_sounds <- c(
'cat' = 'meow',
'dog' = 'woof',
'cow' = 'moo'
)
print(animal_sounds['cat'])
# 'meow'
Update: To answer the 2nd portion of the question, you can create a dataframe and compute the values like this:
val1 <- c(5,2,5,2) # Create val1 column
val2 <- c(5,5,2,2) # Create val2 column
df <- data.frame(val1, val2) # create dataframe variable
df['x1^2+x2'] <- val1^2 + val2 # create expression column
Output:
val1 val2 x1^2+x2
1 5 5 30
2 2 5 9
3 5 2 27
4 2 2 6

You can use just data.frame and row.names to do this:
x=data.frame(row.names=c("Hi","Why","water") , val=c(1,5,4))
x["Why",]
[1] 5

In that vectors, matrices, lists, etc. behave as "dictionaries" in R, you can do something like the following:
> (x <- structure(c(5,2),names=c("a","b"))) ## "dictionary"
a b
5 2
> (result <- outer(x,x,function(x1,x2) x1^2+x2))
a b
a 30 27
b 9 6
> result["b","a"]
[1] 9
If you wanted a table as you've shown in your example, just reshape your array...
> library(reshape)
> (dfr <- melt(result,varnames=c("x1","x2")))
x1 x2 value
1 a a 30
2 b a 9
3 a b 27
4 b b 6
> transform(dfr,val1=x[x1],val2=x[x2])
x1 x2 value val1 val2
1 a a 30 5 5
2 b a 9 2 5
3 a b 27 5 2
4 b b 6 2 2

See my answer to a very recent question. In essence, you use environments for this type of functionality.
For the higher dimensional case, you may be better off using an array (twodimensional) if you want the easy syntax for retrieving the result (you can name the rows and columns). As an alternative,you can paste together the two keys with a separator that doesn't occur in them, and then use that as a unique identifier.
To be specific, something like this:
tmp<-data.frame(x=c("a", "b"), val=c(5,2))
tmp2<-outer(seq(nrow(tmp)), seq(nrow(tmp)), function(lhs, rhs){tmp$val[lhs] + tmp$val[rhs]})
dimnames(tmp2)<-list(tmp$x, tmp$x)
tmp2
tmp2["a", "b"]

Using tidyverse
Adding an answer using more recent tidyverse approaches.
There are probably cleaner ways of handling the crossing (which creates all combinations) and unnesting, but this is a quick and dirty approach.
library(tidyverse)
my_tbl <- tibble(x = c("A", "B"), val=c(5,2)) %>%
crossing(x1 = ., x2 = .) %>% # Create all combinations
unnest_wider(everything(), names_sep="_") %>% # Unpack into distinct columns
mutate(result = x1_val^2 + x2_val) # Calculate result
# Access result by accessing the row in the data frame
my_tbl %>%
filter(x1_x == "A", x2_x == "B") %>%
pull(result)
#> [1] 27
# Convert tibble to a named vector that could be accessed more easily.
# However, this is limited to string names.
my_named_vector <- my_tbl %>%
transmute(name = str_c(x1_x, "_", x2_x), value=result) %>%
deframe()
my_named_vector[["A_B"]]
#> [1] 27
Created on 2022-04-06 by the reprex package (v2.0.1)
tibble version 3.1.6
dplyr version 1.0.8
tidyr version 1.2.0
stringr version 1.4.0

Related

What data type is the json file content in R language?

I have a json file on my computer.
Is my output a dictionary or list or string?
Below is my code in Rstudio:
install.packages("rjson")
library("rjson")
output <- fromJSON(file = "provdata.json")
It depends on the json content.
## vector
rjson::fromJSON('[1,2,3]')
# [1] 1 2 3
## list
rjson::fromJSON('[1,2,3]', simplify = FALSE)
# [[1]]
# [1] 1
# [[2]]
# [1] 2
# [[3]]
# [1] 3
## another list
rjson::fromJSON('[[1,2,3]]')
# [[1]]
# [1] 1 2 3
## dictionary, aka named-list
rjson::fromJSON('{"a":1,"b":2}')
# $a
# [1] 1
# $b
# [1] 2

is there a way I can recycle elements of the shorter list in purrr:: map2 or purrr::walk2?

purrr does not seem to support recycling of elements of a vector in case there is a shortage of elements in one of the two (while using purrr::map2 or purrr::walk2). Unlike baseR where we just get a warning if the larger vector is not a multiple of the shorter one.
Consider this toy example:
This works:
map2(1:3,4:6,sum)
#
#[[1]]
#[1] 5
#[[2]]
#[1] 7
#[[3]]
#[1] 9
And this doesn't work:
map2(1:3,4:9,sum)
Error: .x (3) and .y (6) are different lengths
I understand very well why this is not allowed - as it can make catching bugs very difficult. But is there any way in purrr I can force this to happen? Perhaps using some base R trick with purrr?
You can put both lists in a data frame and let that command repeat your vectors:
input <- data.frame(a = 1:3, b = 4:9)
purrr::map2(input$a, input$b, sum)
It's by design with purrr but you can use Map :
Map(sum,1:3,4:9)
# [[1]]
# [1] 5
#
# [[2]]
# [1] 7
#
# [[3]]
# [1] 9
#
# [[4]]
# [1] 8
#
# [[5]]
# [1] 10
#
# [[6]]
# [1] 12
And here's how I would recycle if I had to :
x <- 1:3
y <- 4:9
l <- max(length(y), length(x))
map2(rep(x,len = l), rep(y,len = l),sum)
# [[1]]
# [1] 5
#
# [[2]]
# [1] 7
#
# [[3]]
# [1] 9
#
# [[4]]
# [1] 8
#
# [[5]]
# [1] 10
#
# [[6]]
# [1] 12

How to check that element of a list of lists matches a condition?

I have a list of lists:
pairs <- list(
list(Name="A",Value=11),
list(Name="B",Value=17),
list(Name="C",Value=23)
)
How can I check that pairs list contains an element with Name=="A"?
And I'd also like to get that element.
If you just want to know whether any list component has Name=='A':
any(sapply(pairs,function(x) x$Name=='A'));
## [1] TRUE
If you want the number of list components that have Name=='A':
sum(sapply(pairs,function(x) x$Name=='A'));
## [1] 1
If you want the Value of the list component(s) that have Name=='A':
unlist(lapply(pairs,function(x) if (x$Name=='A') x$Value));
## [1] 11
If you want the sublist of components that have Name=='A':
pairs[sapply(pairs,function(x) x$Name=='A')];
## [[1]]
## [[1]]$Name
## [1] "A"
##
## [[1]]$Value
## [1] 11
If you want the first inner list that has Name=='A' (can drop the [1] if you're certain there will only be one match):
pairs[[which(sapply(pairs,function(x) x$Name=='A'))[1]]];
## $Name
## [1] "A"
##
## $Value
## [1] 11
Alternatively, since your data appears to be regular, you can convert to a data.frame, which will simplify all these operations:
df <- do.call(rbind,lapply(pairs,as.data.frame));
df;
## Name Value
## 1 A 11
## 2 B 17
## 3 C 23
Here are the equivalents for df:
any(df$Name=='A');
## [1] TRUE
sum(df$Name=='A');
## [1] 1
df$Value[df$Name=='A'];
## [1] 11
subset(df,Name=='A');
## Name Value
## 1 A 11
subset(df,Name=='A')[1,];
## Name Value
## 1 A 11
You can simply use Filter if your list of lists has only one level. this will return the desired element(s):
> Filter(function(u) u$Name=='A', pairs)
#[[1]]
#[[1]]$Name
#[1] "A"
#[[1]]$Value
#[1] 11
You can use rlist
library(rlist)
list.filter(pairs, Name=='A')
#[[1]]
#[[1]]$Name
#[1] "A"
#[[1]]$Value
#[1] 11
Also, my original version is
sapply(pairs, function(x) x[grep('Name',names(x))]=='A')
# Name Name Name
# TRUE FALSE FALSE

R : environment to data.frame

I've an environment object in R and I want convert it to a data.frame. I tried as.data.frame but it didn't accept environment object.. Anyone has an idea ?
e <- new.env(hash=TRUE,size=3)
assign(x="a",value=10,envir=e)
assign(x="b",value=100,envir=e)
assign(x="c",value=1000,envir=e)
Thanks
Perform an intermediate step by converting an environment object to a list:
as.data.frame(as.list(e))
## c a b
## 1 1000 10 100
BTW: In fact, each data frame is represented by a list (consisting of atomic vectors of the same lengths) with a row.names attribute set:
x <- data.frame(v1=1:2, v2=c("a", "b"))
unclass(x)
## $v1
## [1] 1 2
##
## $v2
## [1] a b
## Levels: a b
##
## attr(,"row.names")
## [1] 1 2
typeof(x)
## [1] "list"
mode(x)
## [1] "list"
is.list(x)
## [1] TRUE
And conversely:
x <- list(v1=1:2, v2=c("a", "b"))
attr(x, 'row.names') <- as.character(1:2)
class(x) <- 'data.frame'
print(x)
## v1 v2
## 1 1 a
## 2 2 b
I went a more convoluted route and will share as it may be educational, but is not the best approach for sure:
data.frame(setNames(lapply(ls(e), get, envir=e), ls(e)))

How to get the frequency of items in a list with the different length

Suppose I have the following list:
test<-list(c("a","b","c"),c("a"),c("c"))
>test
[[1]]
[1] "a" "b" "c"
[[2]]
[1] "a"
[[3]]
[1] "c"
What do I do(or functions to use) to get the frequency of unique items in a list like this:?
a 2
b 1
c 2
I tried using table(test), but I get the following error
> table(test)
Error in table(test) : all arguments must have the same length
test <- list(c("a", "b", "c"), c("a"), c("c"))
# If you want count accross all elements
table(unlist(test))
##
## a b c
## 2 1 2
# If you want seperate counts in each item of list
lapply(test, table)
## [[1]]
##
## a b c
## 1 1 1
##
## [[2]]
##
## a
## 1
##
## [[3]]
##
## c
## 1
##
Use unlist first
table(unlist(test))

Resources