web-dev-qa-db-fra.com

Options de mise en cache / mémorisation / hachage dans R

J'essaie de trouver un moyen simple d'utiliser quelque chose comme les fonctions de hachage de Perl dans R (essentiellement la mise en cache), car j'avais l'intention de faire à la fois le hachage de style Perl et d'écrire ma propre mémoisation des calculs. Cependant, d'autres m'ont battu au poing et ont des paquets pour la mémoisation. Plus je creuse, plus j'en trouve, par exemple .memoise et R.cache, mais les différences ne sont pas évidentes. De plus, on ne sait pas trop comment on peut obtenir des hachages de style Perl (ou des dictionnaires de style Python) et écrire sa propre mémorisation, à part utiliser le package hash, qui ne semble pas sous-tendre les deux packages de mémorisation.

Étant donné que je ne trouve aucune information sur CRAN ou ailleurs pour distinguer les options, cela devrait peut-être être une question wiki communautaire sur SO: quelles sont les options de mémorisation et de mise en cache dans R, et quelles sont leurs différences?


Comme base de comparaison, voici une liste des options que j'ai trouvées. De plus, il me semble que tout dépend du hachage, donc je noterai également les options de hachage. Le stockage de clé/valeur est quelque peu lié, mais ouvre une énorme boîte de vers concernant les systèmes de base de données (par exemple BerkeleyDB, Redis, MemcacheDB et des dizaines d'autres ).

Il semble que les options soient les suivantes:

Hachage

  • digest - fournit un hachage pour les objets R arbitraires.

Mémorisation

  • memoise - un outil très simple pour la mémorisation des fonctions.
  • R.cache - offre plus de fonctionnalités pour la mémorisation, bien qu'il semble que certaines fonctions manquent d'exemples.

Mise en cache

  • hash - Fournit une fonctionnalité de mise en cache semblable aux hachages de Perl et aux dictionnaires Python.

Stockage de clé/valeur

Ce sont des options de base pour le stockage externe des objets R.

Checkpointing

Autre

  • Base R prend en charge: les vecteurs et listes nommés, les noms de lignes et de colonnes des trames de données et les noms des éléments dans les environnements. Il me semble que l'utilisation d'une liste est un peu compliquée. (Il y a aussi pairlist, mais il est déconseillé .)
  • Le package data.table prend en charge les recherches rapides d'éléments dans une table de données.

Cas d'utilisation

Bien que je sois surtout intéressé à connaître les options, j'ai deux cas d'utilisation de base qui se présentent:

  1. Mise en cache: comptage simple des chaînes. [Remarque: Ce n'est pas pour la PNL, mais pour une utilisation générale, donc les bibliothèques PNL sont exagérées; les tables sont inadéquates car je préfère ne pas attendre que l'ensemble des chaînes soit chargé en mémoire. Les hachages de type Perl sont au bon niveau d'utilité.]
  2. Mémorisation de calculs monstrueux.

Celles-ci surviennent vraiment parce que je suis creusant dans le profilage d'un code slooooow et j'aimerais vraiment compter les chaînes simples et voir si je peux accélérer certains calculs via la mémorisation. Le fait de pouvoir hacher les valeurs d'entrée, même si je ne mémorise pas, me permettrait de voir si la mémorisation peut aider.


Remarque 1: Vue des tâches CRAN sur la recherche en matière de reproduction répertorie quelques packages (cacher et R.cache), mais il n'y a pas d'élaboration sur les options d'utilisation.

Note 2: Pour aider ceux qui recherchent du code associé, voici quelques notes sur certains des auteurs ou packages. Certains auteurs utilisent SO. :)

  • Dirk Eddelbuettel: digest - beaucoup d'autres packages en dépendent.
  • Roger Peng: cacher, filehash, stashR - ceux-ci abordent différents problèmes de différentes manières; voir site de Roger pour plus de paquets.
  • Christopher Brown: hash - Semble être un package utile, mais les liens vers ODG sont malheureusement en panne.
  • Henrik Bengtsson: R.cache & Hadley Wickham: memoise - il n'est pas encore clair quand préférer un paquet à l'autre.

Remarque 3: Certaines personnes utilisent memoise/memoisation, d'autres utilisent memoize/memoization. Juste une note si vous cherchez autour. Henrik utilise "z" et Hadley utilise "s".

72
Iterator

Je n'ai pas eu de chance avec memoise car cela a donné too deep recursive problème avec une fonction d'un paquet avec lequel j'ai essayé. Avec R.cache J'ai eu plus de chance. Voici un code plus annoté que j'ai adapté de R.cache Documentation. Le code affiche différentes options pour effectuer la mise en cache.

# Workaround to avoid question when loading R.cache library
dir.create(path="~/.Rcache", showWarnings=F) 
library("R.cache")
setCacheRootPath(path="./.Rcache") # Create .Rcache at current working dir
# In case we need the cache path, but not used in this example.
cache.root = getCacheRootPath() 
simulate <- function(mean, sd) {
    # 1. Try to load cached data, if already generated
    key <- list(mean, sd)
    data <- loadCache(key)
    if (!is.null(data)) {
        cat("Loaded cached data\n")
        return(data);
    }
    # 2. If not available, generate it.
    cat("Generating data from scratch...")
    data <- rnorm(1000, mean=mean, sd=sd)
    Sys.sleep(1) # Emulate slow algorithm
    cat("ok\n")
    saveCache(data, key=key, comment="simulate()")
    data;
}
data <- simulate(2.3, 3.0)
data <- simulate(2.3, 3.5)
a = 2.3
b = 3.0
data <- simulate(a, b) # Will load cached data, params are checked by value
# Clean up
file.remove(findCache(key=list(2.3,3.0)))
file.remove(findCache(key=list(2.3,3.5)))

simulate2 <- function(mean, sd) {
    data <- rnorm(1000, mean=mean, sd=sd)
    Sys.sleep(1) # Emulate slow algorithm
    cat("Done generating data from scratch\n")
    data;
}
# Easy step to memoize a function
# aslo possible to resassign function name.
This would work with any functions from external packages. 
mzs <- addMemoization(simulate2)

data <- mzs(2.3, 3.0)
data <- mzs(2.3, 3.5)
data <- mzs(2.3, 3.0) # Will load cached data
# aslo possible to resassign function name.
# but different memoizations of the same 
# function will return the same cache result
# if input params are the same
simulate2 <- addMemoization(simulate2)
data <- simulate2(2.3, 3.0)

# If the expression being evaluated depends on
# "input" objects, then these must be be specified
# explicitly as "key" objects.
for (ii in 1:2) {
    for (kk in 1:3) {
        cat(sprintf("Iteration #%d:\n", kk))
        res <- evalWithMemoization({
            cat("Evaluating expression...")
            a <- kk
            Sys.sleep(1)
            cat("done\n")
            a
        }, key=list(kk=kk))
        # expressions inside 'res' are skipped on the repeated run
        print(res)
        # Sanity checks
        stopifnot(a == kk)
        # Clean up
        rm(a)
    } # for (kk ...)
} # for (ii ...)
9
biocyberman

Pour un simple comptage de chaînes (et sans utiliser table ou similaire), une structure de données multiset semble être un bon ajustement. L'objet environment peut être utilisé pour émuler cela.

# Define the insert function for a multiset
msetInsert <- function(mset, s) {
    if (exists(s, mset, inherits=FALSE)) {
        mset[[s]] <- mset[[s]] + 1L
    } else {
        mset[[s]] <- 1L 
    }
}

# First we generate a bunch of strings
n <- 1e5L  # Total number of strings
nus <- 1e3L  # Number of unique strings
ustrs <- paste("Str", seq_len(nus))

set.seed(42)
strs <- sample(ustrs, n, replace=TRUE)


# Now we use an environment as our multiset    
mset <- new.env(TRUE, emptyenv()) # Ensure hashing is enabled

# ...and insert the strings one by one...
for (s in strs) {
    msetInsert(mset, s)
}

# Now we should have nus unique strings in the multiset    
identical(nus, length(mset))

# And the names should be correct
identical(sort(ustrs), sort(names(as.list(mset))))

# ...And an example of getting the count for a specific string
mset[["Str 3"]] # "Str 3" instance count (97)
9
Tommy

Lié à @ solution biocyperman . R.cache a une fonction d'habillage pour éviter le chargement, l'enregistrement et l'évaluation du cache. Voir la fonction modifiée:

R.cache fournit un wrapper pour le chargement, l'évaluation et l'enregistrement. Vous pouvez simplifier votre code comme ça:

simulate <- function(mean, sd) {
key <- list(mean, sd)
data <- evalWithMemoization(key = key, expr = {
    cat("Generating data from scratch...")
    data <- rnorm(1000, mean=mean, sd=sd)
    Sys.sleep(1) # Emulate slow algorithm
    cat("ok\n")
    data})
}
1
user890739