Para reunir requisitos em um único lugar e atualizar discussões de aproximadamente 4 anos criando este problema para cobrir o recurso de _funções de rolagem_ (também conhecido como _agregados de rolagem_, _janela deslizante_ ou _média móvel _ / _ agregados de movimentação_).
Implementação de rollmean
proposta, simplificada.
x = data.table(v1=1:5, v2=1:5)
k = c(2, 3)
i - single column
j - single window
m - int referring to single row
w - current row's sum of rolling window
r - answer for each i, j
md5-be70673ef4a3bb883d4f334bd8fadec9
for i in x
for j in k
r = NA_real_
w = 0
for m in 1:length(i)
w = w + i[m]
w = w - i[m-j]
r[m] = w / j
sim, e muitas outras funções roladas seguem a mesma ideia básica (incluindo
desvio padrão rotativo / qualquer momento baseado em expectativa e qualquer função
como rollproduct que usa invertível * em vez de + para agregar dentro
a janela
Sempre imaginei a funcionalidade de janela contínua agrupando o conjunto de dados em vários grupos sobrepostos (janelas). Então, a API seria parecida com isto:
DT[i, j,
by = roll(width=5, align="center")]
Então, se j
contém, digamos, mean(A)
, podemos substituí-lo internamente por rollmean(A)
- exatamente como estamos fazendo com gmean()
agora. Ou j
pode conter uma funcionalidade arbitrariamente complicada (digamos, executar uma regressão para cada janela), caso em que forneceríamos .SD
data.table a ela - exatamente como fazemos com grupos agora mesmo.
Dessa forma, não há necessidade de introduzir mais de 10 funções novas, apenas uma. E parece data.table-y em espírito também.
sim, concordo
No sábado, 21 de abril de 2018, 15:38 Pasha Stetsenko [email protected]
escreveu:
Sempre imaginei a funcionalidade de janela rolante como o agrupamento do
conjunto de dados em vários grupos sobrepostos (janelas). Então a API pareceria
algo assim:DT [i, j,
por = rolo (largura = 5, alinhar = "centro")]Então, se j contém, digamos, média (A), podemos substituí-lo internamente por
rollmean (A) - exatamente como estamos fazendo com gmean () agora. Ou eu posso
contém uma funcionalidade arbitrariamente complicada (digamos, execute uma regressão para
cada janela), caso em que forneceríamos .SD data.table para ele - exatamente
como fazemos com grupos agora.Dessa forma, não há necessidade de introduzir mais de 10 funções novas, apenas uma. E isso
sente data.table-y em espírito também.-
Você está recebendo isso porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/Rdatatable/data.table/issues/2778#issuecomment-383275134 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AHQQdbADiE4aAI1qPxPnFXUM5gR-0w2Tks5tquH8gaJpZM4TeTQf
.
@st-pasha ideia interessante, parece o espírito data.table-y, mas vai impor muitas limitações e não é realmente apropriada para esta categoria de funções.
DT[, rollmean(V1, 3), by=V2]
DT[, .(rollmean(V1, 3), rollmean(V2, 100))]
[.data.table
, pois agora permitimos a mudançarollmean(rnorm(10), 3)
DT[, .(rollmean(list(V1, V2), c(5, 20)), rollmean(list(V2, V3), c(10, 30)))]
mean
e rollmean
na mesma chamada de j
DT[, .(rollmean(V1, 3), mean(V1)), by=V2]
Normalmente, ao usar by
, agregamos dados a um número menor de linhas, enquanto as funções de rolagem sempre retornam o vetor do mesmo comprimento da entrada. Esses tipos de funções em SQL têm API no seguinte estilo:
SELECT AVG(value) OVER (ROWS BETWEEN 99 PRECEDING AND CURRENT ROW)
FROM tablename;
Você ainda pode combiná-lo com GROUP BY da seguinte maneira:
SELECT AVG(value) OVER (ROWS BETWEEN 99 PRECEDING AND CURRENT ROW)
FROM tablename
GROUP BY group_columns;
Portanto, no SQL essas funções permanecem em SELECT
que se refere a j
na TD.
Na DT, podemos conseguir o mesmo com:
DT[, rollmean(value, 100)]
DT[, rollmean(value, 100), group_columns]
As funções rotativas se enquadram na mesma categoria de funções de shift
que também retorna o mesmo número de linhas que obteve na entrada.
Shift no SQL se parece com:
SELECT LAG(value, 1) OVER ()
FROM tablename;
mean
e rollmean
não são apenas funções diferentes, são categorias diferentes de funções. Um pretendia agregar de acordo com o grupo, outro não agregar de forma alguma. Isso é facilmente visível em SQL, onde não usamos GROUP BY
para o tipo de funções de rolagem, mas precisamos usar GROUP BY
para agregados como mean
(eventualmente obtendo concessão total ao agrupar cláusula não está presente).
Não vejo um forte raciocínio para tentar aplicar as mesmas regras de otimização que aplicamos para mean
, especialmente quando realmente não se encaixa no caso de uso, e tudo isso apenas por causa de data.table-y espírito. A proposta atual também é o espírito data.table-y, que pode facilmente ser combinada com :=
, o mesmo que shift
. Ele apenas adiciona um conjunto de novas funções, atualmente não disponíveis em data.table.
@jangorecki Obrigado, todas essas são considerações válidas. É claro que pessoas diferentes têm experiências diferentes e visões diferentes sobre o que deve ser considerado "natural".
É possível realizar rollmean por grupo: este é apenas um agrupamento de 2 níveis: DT[, mean(V1), by=.(V2, roll(3))]
. No entanto, não vejo como fazer tamanhos de janela diferentes em colunas diferentes com a minha sintaxe ...
Devo admitir que nunca vi a sintaxe SQL para junções rotativas antes. É interessante que eles usem um agregador padrão, como AVG
mas apliquem a especificação de janelas a ele. Olhando para a documentação do roll()
resultaria em menos repetição.
Além disso, esta pergunta SO fornece uma visão interessante do motivo pelo qual a sintaxe OVER foi introduzida no SQL:
Você pode usar GROUP BY SalesOrderID. A diferença é que com GROUP BY você só pode ter os valores agregados para as colunas que não estão incluídas em GROUP BY. Em contraste, usando funções de agregação em janela em vez de GROUP BY, você pode recuperar valores agregados e não agregados. Ou seja, embora você não esteja fazendo isso em sua consulta de exemplo, você pode recuperar os valores OrderQty individuais e suas somas, contagens, médias etc. sobre grupos dos mesmos SalesOrderIDs.
Portanto, parece que a sintaxe foi projetada para contornar a limitação do SQL padrão, onde os resultados agrupados não podiam ser combinados com valores não agregados (ou seja, selecionando A
e mean(A)
na mesma expressão). No entanto, data.table
não tem essa limitação, portanto, tem mais liberdade na escolha da sintaxe.
Agora, se queremos realmente ficar à frente da curva, precisamos pensar em uma perspectiva mais ampla: o que são as funções "rolantes", para que são usadas, como podem ser estendidas etc. do ponto de vista de um estatístico:
A função "Média contínua" é usada para suavizar algumas entradas com ruído. Digamos, se você tem observações ao longo do tempo e deseja ter alguma noção de "quantidade média", que, no entanto, variaria com o tempo, embora muito lentamente. Neste caso, "média móvel das últimas 100 observações" ou "média móvel de todas as observações anteriores" pode ser considerada. Da mesma forma, se você observar certa quantidade em uma faixa de entradas, poderá suavizá-la aplicando "média móvel em ± 50 observações".
Todos eles podem ser implementados como operadores de agrupamento estendido, com janelas rolantes sendo apenas um dos elementos desta lista. Dito isso, não sei por que não podemos ter as duas coisas.
Devo admitir que nunca vi a sintaxe SQL para junções rotativas antes.
Suponho que você quer dizer funções rotativas, o problema não tem nada a ver com junções rotativas.
Eles permitem diferentes operadores "OVER" em diferentes colunas, no entanto, em todos os exemplos que eles fornecem, é a mesma cláusula OVER repetida várias vezes. Portanto, isso sugere que esse caso de uso é muito mais comum e, portanto, usar um único grupo roll () resultaria em menos repetição.
É apenas uma questão de caso de uso, se você estiver chamando o mesmo OVER () muitas vezes, poderá achar mais eficiente usar GROUP BY
, construir uma tabela de pesquisa e reutilizar em outras consultas. Quaisquer que sejam os exemplos, repetir OVER () é necessário para manter o recurso de localidade para cada medida fornecida. Meus casos de uso de Data Warehouses não foram tão simples quanto os de documentos da Microsoft.
Em contraste, usando funções de agregação em janela em vez de GROUP BY, você pode recuperar valores agregados e não agregados.
Em data.table, fazemos :=
e by
em uma consulta para obtê-lo.
Portanto, parece que a sintaxe é projetada para contornar a limitação do SQL padrão, onde os resultados agrupados não podem ser combinados com valores não agregados (ou seja, selecionando A e média (A) na mesma expressão). No entanto, data.table não tem essa limitação, portanto, tem mais liberdade na escolha da sintaxe.
Não é muita limitação de SQL, mas apenas design de GROUP BY, que irá agregar, da mesma forma que nossos by
agregam. Uma nova API foi necessária para cobrir novas funcionalidades de janela. O agrupamento para a função de janela SQL pode ser fornecido para cada chamada de função usando FUN() OVER (PARTITION BY ...)
onde _partição por_ é como agrupamento local para medida única. Portanto, para alcançar a flexibilidade do SQL, precisaríamos usar j = mean(V1, roll=5)
ou j = over(mean(V1), roll=5)
mantendo essa API em j
. Ainda assim, esta abordagem não permitirá o suporte a todos os casos de uso mencionados acima.
você pode suavizá-lo aplicando "média móvel sobre ± 50 observações".
É para isso que o argumento align
é usado.
Portanto, a primeira extensão é olhar para "janelas suaves": imagine uma média sobre as observações anteriores em que quanto mais longe uma observação no passado, menor é sua contribuição. Ou uma média de observações próximas sobre um kernel gaussiano.
Existem muitas variantes (número virtualmente ilimitado) de médias móveis, a função de janela de suavização mais comum (diferente de rollmean / SMA) é a média móvel exponencial (EMA). Qual deve ser incluído, e qual não, não é trivial de decidir, e na verdade é melhor tomar essa decisão de acordo com as solicitações de recursos que virão dos usuários, até agora nada como esse foi solicitado.
Todos eles podem ser implementados como operadores de agrupamento estendido, com janelas rolantes sendo apenas um dos elementos desta lista.
Certamente podem, mas se você olhar o SO e os problemas criados em nosso repo, verá que essas poucas funções rotativas aqui são responsáveis por mais de 95% das solicitações dos usuários. Estou feliz em trabalhar no EMA e em outros MAs (embora não tenha certeza se data.table é o melhor lugar para eles), mas como uma questão separada. Alguns usuários, inclusive eu, estão esperando apenas uma média móvel simples em data.table há 4 anos.
Aqui está minha opinião, vindo do ponto de vista de um estatístico
Meu ponto de vista vem do Data Warehousing (onde usei a função de janela, pelo menos uma vez por semana) e da análise de tendência de preços (onde usei dezenas de médias móveis diferentes).
rollmean
draft é enviado para roll
branch. Descobri que a maioria dos outros pacotes que implementam a média móvel não são capazes de lidar bem com na.rm=FALSE
e NAs presentes na entrada. Esta implementação trata NA consistentemente com mean
, o que impõe alguma sobrecarga extra por causa de ISNAN
chamadas. Poderíamos permitir uma versão mais rápida, porém menos segura, da API se o usuário tiver certeza de que não há NAs na entrada.
PR está em # 2795
@mattdowle respondendo a perguntas de RP
Por que estamos fazendo isso dentro de data.table? Por que estamos integrando em vez de contribuir com os pacotes existentes e usá-los do data.table?
meu palpite é que se trata de sintaxe (recursos apenas possíveis ou convenientes se integrados em data.table; por exemplo, dentro [...] e otimizado) e construção de data.table internals na função contínua em nível C; por exemplo, froll * deve estar ciente e usar índices e chave data.table. Nesse caso, são necessários mais detalhes; por exemplo, um exemplo simples e curto.
Para mim pessoalmente se trata de velocidade e falta de cadeia de dependências, hoje em dia difícil de conseguir.
Chave / índices podem ser úteis para frollmin / frollmax, mas é improvável que o usuário crie um índice na variável de medida. É improvável que o usuário faça o índice na variável de medida, também não fizemos essa otimização para mín. / Máx. Ainda. Não vejo muito sentido para a otimização GForce porque a memória alocada não é liberada após a chamada *, mas retornada como resposta (em oposição à média não contínua, soma etc.).
Se não houver um argumento convincente para a integração, devemos contribuir para os outros pacotes.
Listei alguns acima, se você não está convencido, recomendo que você preencha uma pergunta para os usuários da data.table, pergunte no twitter, etc. para verificar a resposta. Este recurso foi solicitado há muito tempo e por muitos usuários. Se a resposta não o convencer, você pode encerrar este problema.
Descobri que sparklyr
pode suportar funções dinâmicas muito bem em um conjunto de dados de escala muito grande.
@harryprince poderia
De acordo com a vinheta dplyr de "funções da janela"
Os agregados rotativos operam em uma janela de largura fixa. Você não os encontrará na base R ou no dplyr, mas existem muitas implementações em outros pacotes, como RcppRoll.
AFAIU, você usa a API personalizada do Spark via sparklyr para a qual a interface dplyr não está implementada, correto?
Este problema é sobre agregados contínuos, outros "tipos" de funções de janela já estão em data.table
por um longo tempo.
Fornecer alguns exemplos para que possamos comparar o desempenho (na memória) vs sparklyr
/ SparkR
também seria útil.
Acaba de me ocorrer que esta pergunta:
como calcular diferentes tamanhos de janela para diferentes colunas?
tem, na verdade, um escopo mais amplo e não se aplica apenas a funções rotativas.
Por exemplo, parece perfeitamente razoável perguntar como selecionar o preço médio do produto por data, depois por semana e talvez por semana + categoria - tudo dentro da mesma consulta. Se algum dia implementarmos tal funcionalidade, a sintaxe natural para isso poderia ser
DT[, .( mean(price, by=date),
mean(price, by=week),
mean(price, by=c(week, category)) )]
Agora, se essa funcionalidade já foi implementada, então teria sido um salto simples daí para os meios contínuos:
DT[, .( mean(price, roll=5),
mean(price, roll=20),
mean(price, roll=100) )]
Não estou dizendo que isso é inequivocamente melhor do que rollmean(price, 5)
- apenas acrescentando algumas alternativas a serem consideradas ...
@ st-pasha
como selecionar o preço médio do produto por data, depois por semana e talvez por semana + categoria - tudo dentro da mesma consulta.
AFAIU isso já é possível usando ?groupingsets
, mas não conectado a [.data.table
ainda.
@jangorecki , @st-pasha e Co. - Obrigado por todo o seu trabalho nisso! Estou curioso para saber por que o suporte de janela parcial foi removido do osciloscópio. Existe algum potencial para essa funcionalidade voltar ao roadmap? Pode ser útil para mim às vezes e preencher uma lacuna de funcionalidade que, pelo que sei, não foi preenchida em zoo
ou RcppRoll
.
Esta questão do Stack Overflow é um bom exemplo de um aplicativo contínuo que poderia se beneficiar de um argumento partial = TRUE
.
@msummersgill Obrigado pelo feedback. Na primeira postagem, vinculei explicitamente o commit sha onde o código de recurso da janela parcial pode ser encontrado. A implementação que existe foi removida posteriormente para reduzir a complexidade do código. Ele também estava impondo um pequeno custo de desempenho, mesmo quando não estava usando esse recurso. Este recurso pode (e provavelmente deve) ser implementado de outra maneira, primeiro completo como está e, em seguida, apenas preencher a janela parcial ausente usando o loop extra de 1:window_size
. Portanto, a sobrecarga desse recurso só é perceptível quando você o usa. No entanto, fornecemos essa funcionalidade por meio do argumento adaptive
, onde partial
feature é apenas um caso especial de adaptive
média móvel. Há um exemplo de como alcançar partial
usando adaptive
no manual ?froll
. Colando aqui:
d = as.data.table(list(1:6/2, 3:8/4))
an = function(n, len) c(seq.int(n), rep(n, len-n))
n = an(3, nrow(d))
frollmean(d, n, adaptive=TRUE)
É claro que não será tão eficiente quanto a função de rolagem não adaptativa usando loop extra para preencher apenas a janela parcial.
AFAIK zoo
tem o recurso partial
.
Vocês têm algum plano de adicionar funções de regressão contínua a data.table?
@waynelapierre se houver demanda para isso, então sim. Você tem meu +1
obrigado, isso é ótimo. Porém, apenas uma pergunta. Vejo apenas agregados rotativos simples, como uma média rotativa ou uma mediana rotativa. Você também está implementando funções de rolagem mais refinadas, como dataframes de rolagem do DT? Digamos, crie um DT rolante usando os últimos 10 obs e execute uma regressão lm
nele.
Obrigado!
@randomgambit eu diria que está fora do escopo, a menos que haja alta demanda por isso. Não seria muito difícil fazer isso para ser mais rápido do que a base R / zoo apenas manipulando o loop aninhado em C. Mas devemos tentar implementá-lo usando um algoritmo "online", para evitar o loop aninhado. Isso é mais complicado e poderíamos eventualmente fazer isso para qualquer estatística, então temos que cortar essas estatísticas em algum momento.
@jangorecki obrigado interessante. Isso significa que continuarei usando tsibble
para incorporar ... DATA.TABLES
em um tibble
! mente explodida: D
Tentei usar frollmean
para calcular uma "curva logística" não paramétrica que mostra P[y | x]
para o binário y
usando os vizinhos mais próximos de x
. Ficou surpreso: y
armazenado como logical
não foi lançado automaticamente para integer
:
DT = data.table(x = rnorm(1000), y = runif(1000) > .5)
DT[order(x), .(x, p_y = frollmean(y, 50L))]
Erro em froll (fun = "mean", x = x, n = n, fill = fill, algo = algo, align = align,:
x deve ser do tipo numérico
Um exemplo de como x
/ n
argumentos vetorizados podem afetar o desempenho.
https://github.com/AdrianAntico/RemixAutoML/commit/d8370712591323be01d0c66f34a70040e2867636#r34784427
menos loops, código mais fácil de ler, muito mais rápido (aceleração de 10x-36x).
frollapply pronto: https://github.com/Rdatatable/data.table/pull/3600
### fun mean sum median
# rollfun 8.815 5.151 60.175
# zoo::rollapply 34.373 27.837 88.552
# zoo::roll[fun] 0.215 0.185 NA
# frollapply 5.404 1.419 56.475
# froll[fun] 0.003 0.002 NA
oi pessoal, FUN (definido pelo usuário) passado para frollapply será alterado para retornar um objeto R ou data.frame (data.table), x passado para frollapply poderia ser data.table de caractere não forçado para numérico, então FUN poderia fazer em labels e frollapply retornam uma lista? então podemos fazer regressão ou teste contínuo, como fazer o teste de Benford ou o resumo nos rótulos.
É sempre útil fornecer exemplos reproduzíveis. Para esclarecer ... em tal cenário, você gostaria de frollapply(dt, 3, FUN)
retornar uma lista de comprimento nrow(dt)
onde cada elemento da lista será data.table
retornado por FUN(dt[window])
?
frollapply(x=dt, n=3, fun=FUN)[[3]]
é igual a FUN(dt[1:3])
frollapply(x=dt, n=3, FUN=FUN)[[4]]
é igual a FUN(dt[2:4])
isso está correto? @ jerryfuyu0104
Atualmente, oferecemos suporte a várias colunas passadas para o primeiro argumento, mas as processamos separadamente, em loop. Provavelmente precisaríamos de algum argumento extra multi.var=FALSE
, quando definido como verdadeiro não faria um loop sobre x
(como faz agora: list(FUN(x[[1]]),FUN(x[[2]]))
), mas passaria todas as colunas FUN(x)
.
alguma atualização para isso?
Apoio esse pedido anterior.
Além disso, seria possível apoiar um argumento "parcial" para permitir janelas parciais?
@eliocamp você poderia explicar o que é uma janela partial
?
@eliocamp seria possível apoiar o argumento "parcial". Você já deve saber disso, mas o suporte para esta funcionalidade já existe, usando o argumento adaptive=TRUE
, veja exemplos para detalhes.
Significaria calcular a função do início ao fim, em vez de formar o ponto de meia janela.
Por exemplo, para uma média móvel de largura 11, o primeiro elemento retornado seria a média dos elementos 1 a 6. O segundo, a média do 1º ao 7º e assim por diante.
@jangorecki oh, obrigado, eu não sabia disso! Vou dar uma olhada.
Concordo, precisamos de um argumento parcial, não apenas para conveniência, mas também para velocidade. adaptive=TRUE
adiciona uma sobrecarga.
E sim, também precisamos de regressão rolante, fornecendo várias variáveis e rolando sobre elas de uma vez, não cada uma separadamente.
Não há atualização sobre o status deles.
Eu adoraria ajudar, mas minhas habilidades em C ++ são totalmente inexistentes. : suor: Você acha que pode ser adequado para iniciantes?
Não codificamos em C ++, mas em C. Sim, é um bom lugar para começar. Eu fiz exatamente isso no Frollmean.
Eu olho para o código e parece assustador. Mas vou atualizá-lo em qualquer caso.
Mas agora, para mais uma solicitação: frollmean (.SD) deve preservar os nomes. De forma mais geral, froll * deve preservar os nomes se a entrada for como uma lista com nomes.
Como um usuário frequente de data.table, acho extremamente útil ter recursos "conscientes do tempo", como os oferecidos atualmente no pacote tsibble
. Infelizmente, este pacote foi desenvolvido em torno de dplyr
. Eu me pergunto se uma implementação data.table poderia ser possível. As funções de janela propostas nesta edição são um subconjunto desses recursos.
@ywhcuhk Obrigado pelo feedback, na verdade eu estava pensando que esse problema já estava pedindo demais. A maior parte disso é bem coberta por um rolo de embalagem ainda leve, que é muito rápido. Quanto aos outros recursos, sugiro criar um novo problema para cada recurso de seu interesse, portanto, a discussão se queremos implementar / manter pode ser decidida para cada um separadamente. Só de olhar para o readme de tstibble, não vejo nada de novo que ele ofereça ...
Seu título é "Tidy Temporal Data Frames", mas nem parece oferecer junções temporais.
Obrigado @jangorecki pela resposta. Talvez seja uma questão dependente do contexto. A estrutura de dados com a qual lido com mais frequência é conhecida como "dados de painel", com um ID e um tempo. Se o programa estiver "ciente" deste recurso de dados, muitas operações, especialmente operações de série temporal, serão facilitadas. Para quem conhece STATA, são as operações baseadas em tsset
e xtset
, como lead, lag, fill gap, etc. Acho que o "índice" na tabela de dados pode ser aprimorado de alguma forma para permitir tais operações.
Claro, essas operações podem ser feitas em funções data.table como shift
e by
. Achei que index
em data.table tem muito potencial a ser explorado. Eu concordo que isso deveria pertencer a uma questão diferente. Mas não sei como movê-lo sem perder as discussões acima ...
Comentários muito úteis
@mattdowle respondendo a perguntas de RP
Para mim pessoalmente se trata de velocidade e falta de cadeia de dependências, hoje em dia difícil de conseguir.
Chave / índices podem ser úteis para frollmin / frollmax, mas é improvável que o usuário crie um índice na variável de medida. É improvável que o usuário faça o índice na variável de medida, também não fizemos essa otimização para mín. / Máx. Ainda. Não vejo muito sentido para a otimização GForce porque a memória alocada não é liberada após a chamada *, mas retornada como resposta (em oposição à média não contínua, soma etc.).
Listei alguns acima, se você não está convencido, recomendo que você preencha uma pergunta para os usuários da data.table, pergunte no twitter, etc. para verificar a resposta. Este recurso foi solicitado há muito tempo e por muitos usuários. Se a resposta não o convencer, você pode encerrar este problema.