OpenCPU и jsonlite: /json на основе столбцов или на основе строк

Есть ли простой способ изменить параметр постфикса по умолчанию «/ json» в data.frames, чтобы он был основан на столбцах, а не на основе строк?

Data.frames в R, если я правильно понимаю, на самом деле просто именованные списки, где каждый список имеет ту же длину, что и другие. С помощью jsonlite просто показать разницу (тривиальный пример, да):

library(jsonlite)
ll <- list(xx=1:3, yy=6:8)
dd <- data.frame(xx=1:3, yy=6:8)
toJSON(dd)
# [1] "[ { \"xx\" : 1, \"yy\" : 6 }, { \"xx\" : 2, \"yy\" : 7 }, { \"xx\" : 3, \"yy\" : 8 } ]"
toJSON(ll)
# [1] "{ \"xx\" : [ 1, 2, 3 ], \"yy\" : [ 6, 7, 8 ] }"
toJSON(dd, dataframe='column')
# [1] "{ \"xx\" : [ 1, 2, 3 ], \"yy\" : [ 6, 7, 8 ] }"
toJSON(as.list(dd))
# [1] "{ \"xx\" : [ 1, 2, 3 ], \"yy\" : [ 6, 7, 8 ] }"

где последние три одинаковы. Легко заставить его выглядеть одинаково, используя аргумент dataframe для toJSON или заменив data.frame на list.

При использовании API OpenCPU вызовы выглядят одинаково:

$ curl http://localhost:7177/ocpu/library/base/R/list/json -H "Content-Type: application/json" -d '{ "xx":[1,2,3], "yy":[6,7,8] }'
{
        "xx" : [
                1,
                2,
                3
        ],
        "yy" : [
                6,
                7,
                8
        ]
}

$ curl http://localhost:7177/ocpu/library/base/R/data.frame/json -H "Content-Type: application/json" -d '{ "xx":[1,2,3], "yy":[6,7,8] }'
[
        {
                "xx" : 1,
                "yy" : 6
        },
        {
                "xx" : 2,
                "yy" : 7
        },
        {
                "xx" : 3,
                "yy" : 8
        }
]

Если я хочу, чтобы сам data.frame был основан на столбцах JSON, мне нужно принудить его к list:

$ curl http://localhost:7177/ocpu/library/base/R/data.frame -H "Content-Type: application/json" -d '{ "xx":[1,2,3], "yy":[6,7,8] }'
/ocpu/tmp/x000a0fb8/R/.val
/ocpu/tmp/x000a0fb8/stdout
/ocpu/tmp/x000a0fb8/source
/ocpu/tmp/x000a0fb8/console
/ocpu/tmp/x000a0fb8/info

$ curl http://localhost:7177/ocpu/library/base/R/as.list/json -d "x=x000a0fb8"
{
        "xx" : [
                1,
                2,
                3
        ],
        "yy" : [
                6,
                7,
                8
        ]
}

Три вопроса:

  1. Есть ли способ изменить поведение авто-JSON-ификации OpenCPU по умолчанию на основе столбцов?

  2. Есть ли причина (кроме того, что «приходилось что-то делать по умолчанию»), по которой по умолчанию используется строка? (Чтобы я мог лучше понять основы и эффективность, а не вызов.)

  3. Тем не менее, это все академический подход, поскольку большинство (если не все) библиотек, принимающих вывод JSON, будут прозрачно понимать и переводить между форматами. Верно?

(Win7 x64, R 3.0.3, opencpu 1.2.3, jsonlite 0.9.4)

(PS: Спасибо, Jeroen, OpenCPU великолепен! Чем больше я играю, тем больше мне нравится.)


person r2evans    schedule 28.03.2014    source источник


Ответы (2)


Для объектов dataframe вы можете использовать HTTP GET и установить аргумент dataframe:

GET http://localhost:7177/ocpu/tmp/x000a0fb8/json?dataframe=rows

Например, объект Boston из пакета MASS также является фреймом данных:

https://cran.ocpu.io/MASS/data/Boston/json?dataframe=columns
https://cran.ocpu.io/MASS/data/Boston/json?dataframe=rows

Для запросов HTTP GET к конечной точке .../json все параметры http сопоставляются с аргументами в функции toJSON из jsonlite. пакет. Вы также можете указать другие toJSON аргументы:

https://cran.ocpu.io/MASS/data/Boston/json?dataframe=columns&digits=4

Чтобы узнать, какие аргументы доступны, обратитесь к руководству по jsonlite или этот пост.

Обратите внимание, что это работает, только если вы выполняете двухэтапную процедуру: сначала HTTP POST для функции, которая возвращает dataframe, а затем извлекаете этот объект в формате json с запросом HTTP GET. Вы не можете указать параметры toJSON при выполнении одношагового ярлыка, в котором вы исправляете запрос POST с помощью /json, потому что в запросах POST параметры HTTP всегда сопоставляются с вызовом функции.

Причина такого значения по умолчанию заключается в том, что структура, основанная на строках, кажется наиболее традиционным и совместимым способом кодирования табличных данных. jsonlite paper/vignette содержит более подробную информацию. Обратите внимание, что это также работает и наоборот: вам не нужно вызывать функцию data.frame для создания фрейма данных, просто отправляя аргумент в форме:

[{"xx":1,"yy":6},{"xx":2,"yy":7},{"xx":3,"yy":8}]

автоматически превратит его во фрейм данных:

curl https://public.opencpu.org/ocpu/library/base/R/summary/console -d object='[{"xx":1,"yy":6},{"xx":2,"yy":7},{"xx":3,"yy":8}]'
person Jeroen    schedule 28.03.2014
comment
Я видел, что опция цифр добавлена ​​в других примерах, но поспешно предположила, что это опция для исходной функции, а не для toJSON. Это многое проясняет. Спасибо! - person r2evans; 29.03.2014
comment
Кстати, ваша работа над песочницей через RAppArmor потрясающая; Существуют ли планы или усилия по включению стандартизированного способа упрощения добавления методов аутентификации пользователей? (Потенциально как для доступа к методам, так и для доступа к данным.) Я знаю, что это имеет мало общего с вычислительными аспектами OpenCPU, но, безусловно, может помочь во многих ситуациях. (Лично я предпочитаю открытый исходный код и доступность данных и кода, но мой босс и моя зарплата не всегда согласны с этой философией.) - person r2evans; 29.03.2014
comment
Вы имеете в виду в OpenCPU? Облачный сервер opencpu — это просто стандартный http на apache2 и nginx, не должно быть слишком сложно настроить базовую аутентификацию, LDAP или что-то в этом роде. - person Jeroen; 29.03.2014
comment
Мне нужно рассмотреть возможность ограничения доступности функций и данных в R для каждого пользователя и/или для каждой группы. Я пытаюсь реализовать что-то на месте, чтобы заставить это работать сейчас, но я всегда являюсь поклонником плагиата, если это законный и хороший код. Возможно, OAuth2 или что-то в этом роде... - person r2evans; 29.03.2014

Если вы хотите избежать запроса GET, вы можете преобразовать его в Javascript:

var df = [
        {"id":1,"Sepal.Length":5.1,"Sepal.Width":3.5,"Petal.Length":1.4,"Petal.Width":0.2,"Species":"setosa"},
        {"id":2,"Sepal.Length":4.9,"Sepal.Width":3,"Petal.Length":1.4,"Petal.Width":0.2,"Species":"setosa"},
        {"id":3,"Sepal.Length":4.7,"Sepal.Width":3.2,"Petal.Length":1.3,"Petal.Width":0.2,"Species":"setosa"}
        ]

var columns = Object.keys(df[0]);
var dfcolumns = {};
for (i = 0; i < columns.length; i++) {
    var column = [];
    var colname = columns[i];
    for (j = 0; j < df.length; j++) {
        column.push(df[j][colname]);
    }
    dfcolumns[colname] = column;
}

console.log(dfcolumns);

Результат:

{ id: [ 1, 2, 3 ],
  'Sepal.Length': [ 5.1, 4.9, 4.7 ],
  'Sepal.Width': [ 3.5, 3, 3.2 ],
  'Petal.Length': [ 1.4, 1.4, 1.3 ],
  'Petal.Width': [ 0.2, 0.2, 0.2 ],
  Species: [ 'setosa', 'setosa', 'setosa' ] }
person Stéphane Laurent    schedule 28.08.2016