Alternativa più rapida a deparse ()


9

Mantengo un pacchetto che si basa su chiamate ripetute a deparse(control = c("keepNA", "keepInteger")). controlè sempre lo stesso e l'espressione varia. deparse()sembra passare molto tempo a interpretare ripetutamente lo stesso insieme di opzioni con .deparseOpts().

microbenchmark::microbenchmark(
    a = deparse(identity, control = c("keepNA", "keepInteger")),
    b = .deparseOpts(c("keepNA", "keepInteger"))
)
# Unit: microseconds
# expr min  lq  mean median  uq  max neval
#    a 7.2 7.4 8.020    7.5 7.6 55.1   100
#    b 3.0 3.2 3.387    3.4 3.5  6.0   100

Su alcuni sistemi, le .deparseOpts()chiamate ridondanti occupano effettivamente la maggior parte del tempo di esecuzione deparse()( grafico della fiamma qui ).

Vorrei davvero chiamare solo .deparseOpts()una volta e quindi fornire il codice numerico deparse(), ma ciò sembra impossibile senza chiamare .Internal()o invocare direttamente il codice C, nessuno dei quali è ottimale dal punto di vista dello sviluppo del pacchetto.

deparse
# function (expr, width.cutoff = 60L, backtick = mode(expr) %in% 
#     c("call", "expression", "(", "function"), 
#     control = c("keepNA", "keepInteger", "niceNames", 
#         "showAttributes"), nlines = -1L) 
# .Internal(deparse(expr, width.cutoff, backtick, .deparseOpts(control), 
#     nlines))
# <bytecode: 0x0000000006ac27b8>
# <environment: namespace:base>

C'è una soluzione alternativa conveniente?

Risposte:


4

1) Definire una funzione che genera una copia di deparse il cui ambiente è stato ripristinato per trovare una versione modificata di .deparseOpts che è stata impostata per essere uguale alla funzione di identità. In Runabbiamo quindi eseguire quella funzione di creare una deparse2ed eseguire questo. Questo evita l'esecuzione .Internaldiretta.

make_deparse <- function() {
  .deparseOpts <- identity
  environment(deparse) <- environment()
  deparse
}

Run <- function() {
  deparse2 <- make_deparse()
  deparse2(identity, control = 65)
}

# test
Run()

2) Un altro modo per farlo è definire una funzione di costruzione che crei un ambiente in cui inserire una copia modificata deparsee aggiungere una traccia a quella copia ridefinendola .deparseOptscome funzione di identità. Quindi restituire quell'ambiente. Abbiamo quindi una funzione che la utilizza e per questo esempio creiamo una funzione Runper dimostrarla e quindi eseguirla Run. Questo evita di dover usare.Internal

make_deparse_env <- function() {
  e <- environment()
  deparse <- deparse
  suppressMessages(
    trace("deparse", quote(.deparseOpts <- identity), print = FALSE, where = e)
  )
  e
}

Run <- function() {
  e <- make_deparse_env()
  e$deparse(identity, control = 65)
}

# test
Run()

3) Un terzo approccio è quello di ridefinire deparseaggiungendo un nuovo argomento che imposta .deparseOptsun valore predefinito di identitye imposta controlun valore predefinito di 65.

make_deparse65 <- function() {
  deparse2 <- function (expr, width.cutoff = 60L, backtick = mode(expr) %in% 
    c("call", "expression", "(", "function"), 
    control = 65, nlines = -1L, .deparseOpts = identity) {}
  body(deparse2) <- body(deparse)
  deparse2
}

Run <- function() {
  deparse65 <- make_deparse65()
  deparse65(identity)
}

# test
Run()

Wow, è così intelligente !!! Non l'avrei mai pensato! Non vedo davvero l'ora di confrontarlo su tutti i miei sistemi!
landau,

Quando applico (1) e computo l' backtickargomento, il deparsing è 6 volte più veloce! Sto andando con quello. Grazie mille per la soluzione!
landau,

Hmm ..., apparentemente, R CMD checkrileva la .Internal()chiamata nelle funzioni prodotte da (1). Abbastanza facile da aggirare, ho solo bisogno make_deparse()(expr, control = 64, backtick = TRUE). È stupido ricostruire il deparser ogni volta che lo uso, ma è ancora molto più veloce dell'ingenuo che deparse()stavo usando prima.
landau,

Non per me. Ho provato a creare un pacchetto con solo le funzioni make_deparsee Runin (1) e ho funzionato R CMD builde R CMD check --as-cranmeno "R version 3.6.1 Patched (2019-11-18 r77437)"e non mi sono lamentato e non avevo bisogno di soluzioni alternative. Sei sicuro di non fare qualcosa di diverso o che lo sta causando?
G. Grothendieck,

1
E 'stato causato dal codice definendolo al livello superiore: direct_deparse <- make_direct_deparse(). Il codice mostrato nella risposta stava attento a non farlo e lo definiva solo all'interno di una funzione, cioè all'interno Run.
G. Grothendieck,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.