PostgreSQL, R: умножить все строки таблицы для создания данных панели (временных рядов)

У меня есть таблица buildings с 3,2 миллионами строк. Мне нужно расширить эту таблицу до 11 различных периодов, чтобы обрабатывать ее как (сбалансированные) Paneldata. Это означает, что для каждого объекта есть 11 разных лет (с 2000 по 2010 год) для наблюдения. Периоды следует называть:

2000
2001
...
2009
2010

ОПРЕДЕЛЕНИЯ ТАБЛИЦ

CREATE TABLE public.buildings
(
  gid integer NOT NULL DEFAULT nextval('buildings_gid_seq'::regclass),
  osm_id character varying(11),
  name character varying(48),
  type character varying(16),
  geom geometry(MultiPolygon,4326),
  centroid geometry(Point,4326),
  gembez character varying(50),
  gemname character varying(50),
  krsbez character varying(50),
  krsname character varying(50),
  pv boolean,
  gr smallint,
  capac double precision,
  instdate date,
  pvid integer,
  dist double precision,
  gemewz integer,
  n500 integer,
  ibase double precision,
  popden integer,
  instp smallint,
  b2000 double precision,
  b2001 double precision,
  b2002 double precision,
  b2003 double precision,
  b2004 double precision,
  b2005 double precision,
  b2006 double precision,
  b2007 double precision,
  b2008 double precision,
  b2009 double precision,
  b2010 double precision,
  ibase_id integer[],
  ibase_dist integer[],
  CONSTRAINT buildings_pkey PRIMARY KEY (gid)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE public.buildings
  OWNER TO postgres;

CREATE INDEX build_centroid_gix
  ON public.buildings
  USING gist
  (st_transform(centroid, 31467));

CREATE INDEX buildings_geom_idx
  ON public.buildings
  USING gist
  (geom);

Я хочу использовать данные для регрессионного анализа в R.

ibase_id представляет собой массив gid. ibase_dist — связанный массив с расстояниями от gid до объекта. Длина двух массивов всегда одинакова.

gid в массиве принадлежат записям buildings, которые находятся в радиусе 500 м вокруг centroid, центра объекта, и имеют pv=TRUE (что означает, что dist,instdate,instp,capac&pvid равны NOT NULL).

SELECT a.gid AS buildid, array_agg(b.gid) AS ibase_id, array_agg(round(ST_Distance(ST_Transform(a.centroid, 31467), ST_Transform(b.centroid, 31467))::integer)) AS ibase_dist
  FROM buildings a
  LEFT JOIN (SELECT * FROM buildings WHERE pv=TRUE) AS b ON ST_DWithin(ST_Transform(a.centroid, 31467), ST_Transform(b.centroid, 31467), 500.0)
      AND a.gid <> b.gid
  GROUP BY a.gid

Пример:

ibase_id: {3075528,409073,322311,226643,833798,322344,226609};

ibase_dist {290,293,398,494,411,381,384}

UPDATE buildings
SET ibase=SUM(1/s)
FROM unnest(SELECT ibasedist FROM buildings WHERE (SELECT instp 
       FROM buildings 
       WHERE gid IN unnest(ibase_id))<year) s

Для каждого периода учитываются только записи массивов, чьи годы были ДО наблюдаемого периода панельных данных. (Приведенный выше запрос пока не работает, потому что мне нужно сначала объединить массивы) Сейчас два массива содержат информацию за все годы. Вот почему я подумал, что их нужно добавлять к каждому периоду времени, чтобы после расширения до панельных данных я вычислял ibaseдля каждой записи (11x 3,2 миллиона).

Мне не нужны все столбцы для регрессионного анализа. В случае, если это значительно улучшит производительность умножения, мы могли бы придерживаться строк (в основном исключая столбцы геометрии):

   gid integer NOT NULL DEFAULT nextval('buildings_gid_seq'::regclass),
      gembez character varying(50),
      gemname character varying(50),
      krsbez character varying(50),
      krsname character varying(50),
      pv boolean,
      gr smallint,
      capac double precision,
      dist double precision,
      gemewz integer,
      n500 integer,
      ibase double precision,
      popden integer,
      instp smallint,
      b2000 double precision,
      b2001 double precision,
      b2002 double precision,
      b2003 double precision,
      b2004 double precision,
      b2005 double precision,
      b2006 double precision,
      b2007 double precision,
      b2008 double precision,
      b2009 double precision,
      b2010 double precision,
      ibase_id integer[],
      ibase_dist integer[],
      CONSTRAINT buildings_pkey PRIMARY KEY (gid)
    )
    WITH (
      OIDS=FALSE

Подход к решению

У меня была основная идея создать вторую таблицу periods, содержащую 11 разных периодов, и умножить эту таблицу на таблицу buildings. Не уверен, как это реализовать. К сожалению, у меня мало опыта работы с R, и я не использую базу данных. Интерфейса для R пока нет.

Работа с PostgreSQL 9.5beta2, скомпилированным Visual C++ build 1800, 64-bit и R x64 3.2.1


person NewbieNeedsHelp    schedule 06.02.2016    source источник
comment
Я мог бы порекомендовать создать MRE (минимальный воспроизводимый пример), так как это побуждает людей отвечать. Используйте RPostgreSQL и запрашивайте только те столбцы, которые вам нужны, с помощью dbGetQuery. Позже манипулируйте с пакетом R data.table, его синтаксис очень похож на SQL, но гораздо более модульный и быстрый, поскольку он вписывается в память и хорошо написан на C. Вы можете перекрестно соединить свою таблицу с таблицей периодов, чтобы сделать взрыв строки.   -  person jangorecki    schedule 07.02.2016


Ответы (2)


По сути, набор панельных данных представляет собой данные в формате long с повторяющимися годами для каждой записи в качестве столбца времени. Ваша текущая структура имеет формат широкий. В то время как R может преобразовывать этот очень большой набор данных, PostGreSQL может объединять все годы в запросе на объединение с помощью своего движка и передавать набор результатов в R. Обратите внимание, что некоторые типы данных, такие как объекты геометрии и массивы, могут неправильно преобразовываться в типы данных R, поэтому удалите их или преобразуйте в строковые/числовые типы.

Ниже приведен такой SQL-запрос UNION со сложенными годами. Я не совсем уверен, что вы имеете в виду под ibase_id и ibase_dist или аспектом «умножения», но столбец Year добавляется с соответствующим столбцом b. Пусть сценарий R вызывает его через модуль RPostGreSQL.

import("RPostgreSQL")

# CREATE CONNECTION     
drv <- dbDriver("PostgreSQL")
con <- dbConnect(drv, dbname = "postgres",
                 host = "localhost", port = ####,
                 user = "username", password = "password")

strSQL <- "SELECT '2000' As year,  gid, gembez, gemname, krsbez,
                 krsname, pv, gr, capac, dist, gemewz, n500
                 popden, instp, b2000 As b, (1/ibase_dist) As ibase
           FROM public.buildings
           INNER JOIN
                (SELECT a.gid AS buildid, 
                        SUM(round(ST_Distance(
                                              ST_Transform(a.centroid, 31467),  
                                              ST_Transform(b.centroid, 31467)
                                  )::integer)) AS ibase_dist
               FROM buildings a
               LEFT JOIN buildings b 
                      ON ST_DWithin(ST_Transform(a.centroid, 31467), 
                                    ST_Transform(b.centroid, 31467), 500.0)
                    AND a.gid <> b.gid
               WHERE b.pv=True AND b.instp < a.instp
               GROUP BY a.gid) AS distSum
           ON public.buildings.gid = distSum.buildid
           WHERE public.buildings.instp = 2000

           UNION

           ...other SELECT statements for years 2001-2010..."              

# IMPORT QUERY RESULTSET INTO DATAFRAME
df <- dbGetQuery(con, strSQL)

# CLOSE CONNECTION
dbDisconnect(con)

Но убедитесь, что у вас есть необходимая RAM для работы с большим набором данных. Возможно, вам потребуется выделить память соответствующим образом. Кроме того, вы можете итеративно добавлять оператор SELECT каждого года в растущий объект фрейма данных вместо того, чтобы загружать все сразу.

# ...SAME CONNECTION SETUP AS ABOVE...

years = c('2000', '2001', '2002', '2003', '2004', '2005', 
          '2006', '2007', '2008', '2009', '2010')

# CREATES LIST OF YEAR DATA FRAME
dfList = lapply(years, 
                function(y) {
                # NOTICE CONCATENATION OF Y IN SELECT STATEMENT 
                strSQL <- paste0("SELECT '", y, "' As year,  gid, gembez, gemname, krsbez,
                                         krsname, pv, gr, capac, dist, gemewz, n500, 
                                         popden, instp, b", y, ", As b, (1/ibase_dist) As ibase, 
                                  FROM public.buildings
                                  INNER JOIN
                                    (SELECT a.gid AS buildid, 
                                          SUM(round(ST_Distance(
                                              ST_Transform(a.centroid, 31467),  
                                              ST_Transform(b.centroid, 31467)
                                          )::integer)) AS ibase_dist
                                     FROM buildings a
                                     LEFT JOIN buildings b 
                                     ON ST_DWithin(ST_Transform(a.centroid, 31467), 
                                                   ST_Transform(b.centroid, 31467), 500.0)
                                     AND a.gid <> b.gid
                                     WHERE b.pv=True AND b.instp < a.instp
                                     GROUP BY a.gid) AS distSum
                                  ON public.buildings.gid = distSum.buildid
                                  WHERE public.buildings.instp =", y)
                dbGetQuery(con, strSQL)                               
                })

# APPEND LIST OF DATA FRAMES INTO ONE LARGE DATA FRAME              
df <- do.call(rbind, dfList)

# REMOVE PREVIOUS LIST FOR MEMORY RESOURCES
rm(dfList)

# CLOSE CONNECTION
dbDisconnect(con)
person Parfait    schedule 07.02.2016
comment
Помимо b, есть еще один столбец, который должен отличаться от периода к периоду. Это столбец ibase. На данный момент ibase все еще NULL. ibase_id — строка с годами, ibase_dist — строка с расстояниями. Две строки связаны и должны быть математически объединены, чтобы вернуть значение double precision ibase. Для каждого периода учитываются только записи массивов, годы которых были ДО наблюдаемого периода панельных данных. Прямо сейчас две строки содержат информацию за все годы. Вот почему я думал, что они должны быть добавлены к каждому периоду времени. - person NewbieNeedsHelp; 07.02.2016
comment
Можете ли вы опубликовать образец данных о том, как выглядят эти столбцы ibase и о желаемых результатах? Это лучше проиллюстрирует ваше объяснение. Математические операции, безусловно, могут быть добавлены к запросу, но, как уже упоминалось, сложные типы Postgre, такие как массивы, не могут быть перенесены в типы данных R. - person Parfait; 07.02.2016
comment
Как массивы относятся к текущей записи? Соответствуют ли расстояния в ibase_dist или gids в ibase_id группе, к которой принадлежит запись, или какому-то идентификатору, связанному с записью? - person Parfait; 08.02.2016
comment
gid в массиве принадлежат записям buildings, которые находятся в радиусе 500 м вокруг centroid, центра объекта, И имеют pv=TRUE (что означает, что dist, instdate, instp, capac&pvid равны NOT NULL). Я добавил запрос, который создал эти массивы в моем вопросе. - person NewbieNeedsHelp; 08.02.2016
comment
Гораздо, намного яснее. Мы определенно можем присоединиться к исходному запросу. Теперь, что вы имеете в виду под математическим комбинированием? Является ли ibase суммой ibase_dist элементов массива за годы до текущего года записи? - person Parfait; 08.02.2016
comment
Я хочу попробовать различные математические комбинации. Во-первых, ibase=Sum(1/s) с s будет ibase_dist элементами массива за годы до текущего года записи. (Позже я хочу включить другие элементы gid, например capac. Вот почему я хотел массив gid: чтобы оставить другие комбинации открытыми) - person NewbieNeedsHelp; 08.02.2016
comment
См. обновление, в котором добавлена ​​производная таблица, суммирующая ibase_dist по критериям, а затем принимающая обратное значение в расчете ibase. Плюс каждый оператор select является условием на instp года. Совсем не знакомы с массивами Postgre и не рекомендую несколько значений в одном столбце, вы можете справиться со своими потребностями с помощью объединенной таблицы/представления «один ко многим». - person Parfait; 09.02.2016
comment
Не уверен в использовании ", y ," в растущем запросе кадра данных. "SELECT '", y, "' предназначен именно для этого, или " ", , , или ' ' используются случайно? То же самое для b", y, ", As b и WHERE public.buildings.instp =", y . Я спрашиваю, потому что запрос вернул несколько ошибок. Мне нужно суммировать обратные значения расстояний. Это не обратное значение суммы расстояний. Но я исправил, используя Sum(1/(round(ST_Distance....)) AS ibase_dist и удалив (1/...) из (1/ibase_dist) AS ibase - person NewbieNeedsHelp; 10.02.2016

Я создал таблицу Paneldata с помощью перекрестного соединения с временной таблицей t1, содержащей периоды.

CREATE TABLE public.t1
(
  period smallint
)
WITH (
  OIDS=FALSE
);



CREATE TABLE paneldata AS
(SELECT * 
FROM t1 CROSS JOIN 
    (SELECT gid, 
    gemname, 
    gembez, 
    krsname,
    krsbez,
    pv,
    gr,
    capac,
    dist,
    gemewz,
    n500,
    popden,
    instp
    FROM buildings) AS test
ORDER BY gid)
person NewbieNeedsHelp    schedule 16.02.2016