0
votes

Comment remplir des valeurs irrégulièrement manquantes avec une interépolation linéaire dans BigQuery?

J'ai des données qui ont des valeurs manquantes de manière irrégulière et j'aimerais les convertir avec un certain intervalle avec une interpolation de ligne à l'aide de BigQuery Standard SQL.

Plus précisément, j'ai des données comme celles-ci:

# interpolated with interval of 1
+------+--------------------+
| time | value_interpolated |
+------+--------------------+
|    1 | 3.0                |
|    2 | 3.5                |
|    3 | 4.0                |
|    4 | 4.5                |
|    5 | 5.0                |
|    6 | 3.0                |
|    7 | 1.0                |
|    8 | 4.5                |
|    9 | 8.0                |
|   10 | 4.0                |
+------+--------------------+

et j'aimerais convertir ce tableau comme suit:

# data is missing irregulary
+------+-------+
| time | value |
+------+-------+
|    1 | 3.0   |
|    5 | 5.0   |
|    7 | 1.0   |
|    9 | 8.0   |
|   10 | 4.0   |
+------+-------+

Une solution intelligente pour cela?

Supplément: cette question est similaire à cette question dans stackoverflow mais différente en ce que les données manquent de manière irrégulière.

Merci.


5 commentaires

quelle est la logique de mettre 3,0 dans le temps = 6.


Merci pour le commentaire. Il est calculé comme la valeur moyenne du temps = 5 (la valeur est 5,0) et du temps = 7 (la valeur est 1,0)


Pourriez-vous expliquer comment vous êtes arrivé aux valeurs 3,5,4,4,5 pour le moment (2,3,4)


Merci. Il s'agit de données d'interépolation entre time = 1 (la valeur est 3) et time = 5 (value is 5.0) linerarly. Ainsi, l'intervalle de 0,5 dans le premier 3,5, 4,0, 4,5 est calculé comme suit: (valeur 5,0 - valeur 3,0) / (temps 5 - temps 1) = 2/4 = 0,5.


Merci, en suivant cette logique, la valeur du temps = 8 devrait-elle être (valeur 8,0 - valeur 1,0) / (heure 9 - heure 7) = 7/2 = 3,5


3 Réponses :


1
votes

Voici un exemple de résolution de ce problème dans Postgresql.

https://dbfiddle.uk/?rdbms=postgres_9.5&fiddle=c560dd9a8db095920d0a15834b6768f1

 with data
       as (select time
                  ,lead(time) over(order by time) as next_time
                  ,value
                  ,lead(value) over(order by time) as next_value
                  ,(lead(value) over(order by time)- value) as val_diff
                  ,(lead(time) over(order by time)- time) as time_diff
              from t
          )
    select *
           ,generate_series- time as grp
           ,case when generate_series- time = 0 then
                      value
                 else value + (val_diff*1.0/time_diff)*(generate_series-time)*1.0
             end as val_grp
      from data
    cross join  UNNEST(GENERATE_ARRAY(time, coalesce(next_time-1,time))) as generate_series

Je pense que la syntaxe serait différente dans BigQuery en utilisant UNNEST et GENERATE_ARRAY comme suit. Vous pouvez essayer.

with data
   as (select time
              ,lead(time) over(order by time) as next_time
              ,value
              ,lead(value) over(order by time) as next_value
              ,(lead(value) over(order by time)- value) as val_diff
              ,(lead(time) over(order by time)- time) as time_diff
          from t
      )
select *
       ,generate_series- time as grp
       ,case when generate_series- time = 0 then
                  value
             else value + (val_diff*1.0/time_diff)*(generate_series-time)*1.0
         end as val_grp
  from data
cross join generate_series(time, coalesce(next_time-1,time))


+------+-----------------+-----+-------------------------+
| time | generate_series | grp |         val_grp         |
+------+-----------------+-----+-------------------------+
|    1 |               1 |   0 |                     3.0 |
|    1 |               2 |   1 | 3.500000000000000000000 |
|    1 |               3 |   2 | 4.000000000000000000000 |
|    1 |               4 |   3 | 4.500000000000000000000 |
|    5 |               5 |   0 |                     5.0 |
|    5 |               6 |   1 |     3.00000000000000000 |
|    7 |               7 |   0 |                     1.0 |
|    7 |               8 |   1 |     4.50000000000000000 |
|    9 |               9 |   0 |                     8.0 |
|   10 |              10 |   0 |                     4.0 |
+------+-----------------+-----+-------------------------+


0 commentaires

1
votes

Dans BigQuery, vous pouvez générer les lignes supplémentaires pour chaque ligne à l'aide de generate_array() . Ensuite, vous pouvez utiliser lead() pour obtenir des informations de la ligne suivante et un peu d'arithmétique pour l'interpolation:

with t as (
      select 1 as time, 3.0 as value union all
      select 5 , 5.0 union all  
      select 7 , 1.0 union all
      select 9 , 8.0 union all
      select 10 , 4.0 
     ),
     tt as (
      select t.*,
             lead(time) over (order by time) as next_time,
             lead(value) over (order by time) as next_value
      from t
     )
select coalesce(n, tt.time) as time, 
       (case when n = tt.time or n is null then value
             else tt.value + (tt.next_value - tt.value) * (n - tt.time) / (tt.next_time - tt.time)
        end) as value
from tt left join
     unnest(generate_array(tt.time, tt.next_time - 1, 1)) n
     on true
order by 1;

Remarque: vous avez une colonne appelée time qui contient un entier. S'il s'agit vraiment d'un type de données date / heure d'un certain type, je vous suggère de poser une nouvelle question avec des exemples de données plus appropriés et les résultats souhaités - si vous ne voyez pas comment adapter cette réponse.


0 commentaires

1
votes

Ci-dessous, pour BigQuery Standard SQL

#standardSQL
select time,
  ifnull(value, start_value + (end_value - start_value) / (end_tick - start_tick) * (time - start_tick)) as value_interpolated
from (
  select time, value,
    first_value(tick ignore nulls) over win1 as start_tick,
    first_value(value ignore nulls) over win1 as start_value,
    first_value(tick ignore nulls) over win2 as end_tick,
    first_value(value ignore nulls) over win2 as end_value,
  from (
    select time, t.time as tick, value
    from (
      select generate_array(min(time), max(time)) times
      from `project.dataset.table`
    ), unnest(times) time 
    left join `project.dataset.table` t
    using(time)
  )
  window win1 as (order by time desc rows between current row and unbounded following),
  win2 as (order by time rows between current row and unbounded following)
)

si appliquer aux exemples de données de votre question - la sortie est

entrez la description de l'image ici


2 commentaires

Merci. J'ai résolu avec cette réponse. J'ai posté une autre question liée à ce problème (beaucoup plus difficile), j'aimerais que vous la vérifiiez si cela ne vous dérange pas. Le lien pour la nouvelle question est le suivant: stackoverflow.com/questions/64829772/...


sûr. répondu aussi: o)