How to find the average rating based on item name - php

I am working on building simple recommendation engine using PHP and MongoDB. I am trying to find the average rating of each item but I did not figure it out yet.
my collection is:
{
"_id" : 1,
"item" : "efg",
"rating" : 5.0,
"date" : ISODate("2014-01-01T08:00:00.000Z")
}
{
"_id" : 2,
"item" : "efg",
"rating" : 2.0,
"date" : ISODate("2014-01-01T08:00:00.000Z")
}
{
"_id" : 3,
"item" : "abc",
"rating" : 5.0,
"date" : ISODate("2014-01-01T08:00:00.000Z")
}
{
"_id" : 4,
"item" : "abc",
"rating" : 4.0,
"date" : ISODate("2014-01-01T08:00:00.000Z")
}
{
"_id" : 5,
"item" : "xyz",
"rating" : 3.0,
"date" : ISODate("2014-01-01T08:00:00.000Z")
}
This is the code I have tried recently:
$out = $sales->aggregate(
array(
'$group' => array(
'_id' => '$item',
'avgrating' => array('$avg' => '$rating')
)
)
);
var_dump($out);

Please try executing following code snippet
$out = $sales->aggregate(
array(
'$group' => array(
'_id' => array('item' => '$item'),
'avgrating' => array('$avg' => '$rating')
)
)
);
var_dump($out);

I got it working using this code:
$pipeline = array(
array(
'$group' => array(
'_id' => '$itemId',
'avgrating' => array('$avg' => '$rating'),
),
)
);
$out = $sales1->aggregate($pipeline);
var_dump($out)

Related

Get sum of objects with same id inside nested array php

This is my array of objects in php:
Array( [0]=>
Array (
[added_time] => 2018-12-09 14:00:00+00
[id] => 123
[places] =>
[{
"place_id" : 1,
"total" : 5,
"empty" : 0,
"weight" : 68000,
"persons" :
[{
"person_id" : 2,
"person_name" : "ABC",
"total":100
},
{
"person_id" : 3
"person_name" : "DEF",
"total":200
}]
},
"place_id" : 4,
"total" : 10,
"empty" : 0,
"weight" : 54000,
"persons" :
[{
"person_id" : 2,
"person_name" : "ABC"
"total":100
},
{
"person_id" : 6
"person_name" : "GHI",
}
]
],
),
Array (
[added_time] => 2018-12-09 15:00:00+00
[id] => 456
[places] =>
[{
"place_id" : 1,
"total" : 5,
"empty" : 0,
"weight" : 68000,
"persons" :
[{
"person_id" : 2,
"person_name" : "ABC",
"total":200
},
{
"person_id" : 3
"person_name" : "DEF",
"total":300
}]
}]
)
)
I am trying to get sum on the base of person_id like group by person_id.
My desired result should be:
Array (
[added_time] => 2018-12-09 14:00:00+00
[id] => 123
persons:array(
Array([0]=>
[person_id] : 2,
[person_name] : "ABC",
[total]:200
),
Array([1]=>
[person_id] : 3,
[person_name] : "DEF",
[total]:300
),
Array([2]=>
[person_id] : 6,
[person_name] : "GHI",
[total]:500
)
),
[added_time] => 2018-12-09 15:00:00+00
[id] => 123
persons:array(
Array([0]=>
[person_id] : 2,
[person_name] : "ABC",
[total]:200
),
Array([1]=>
[person_id] : 3,
[person_name] : "DEF",
[total]:300
)
)
)
How can I achieve this result. I can use foreach but I don't want to use it because of three iterations.
Is there any other method to achieve this in php and without any performance issue?
You can have that by using PHP function as array-merge, array-column, array-reduce.
Let divided that to 3 step:
1.Sum total for all person given list of persons. Define the the following reduce function:
function reduce($carry, $item)
{
if (!in_array($item["person_name"], array_column($carry, "person_name")))
$carry[] = $item;
else {
foreach($carry as &$person) {
if ($item["person_name"] == $person["person_name"])
$person["total"] += $item["total"];
}
}
return $carry;
}
2.For every element in your array create you output using the reduce from step 1:
function reduceElement($arr) {
$res = array("added_time" => $arr["time"], "id" => $arr["id"]);
$persons = array();
foreach($arr["places"] as $place) {
$persons = array_merge($persons, $place["persons"]);
}
$res["persons"] = array_reduce($persons, "reduce", array());
return $res;
}
3.Combine it all:
$elemA= array("id"=>1, "time"=>"2018", "places" => array(array("persons" => array(array("person_name" => "ABC", "total" => 100), array("person_name" => "DEF", "total" => 200))), array("persons" => array(array("person_name" => "ABC", "total" => 100), array("person_name" => "GHI", "total" => 100)))));
$elemB = array("id"=>2, "time"=>"2017", "places" => array(array("persons" => array(array("person_name" => "ABC", "total" => 200), array("person_name" => "DEF", "total" => 300)))));
$arr = array($elemA, $elemB);
$res = array();
foreach($arr as $obj)
$res[] = reduceElement($obj);
print_r($res);
This will give you the require out put

how to create a pivot array in php

I have the following :
$data = [
{model_num : "ABC", revision : "AA", "testRecipeID":85, value : 31.25, treatment : 'Pressure' },
{model_num : "ABC", revision : "AA", "testRecipeID":85, value : 31.25, treatment : 'Gas' },
{model_num : "ABC", revision : "AA", "testRecipeID":85, value : 33.12, treatment : 'Temp' },
{model_num : "ABC", revision : "AA", "testRecipeID":85, value : 25.87, treatment : 'Current' },
{model_num : "ABC", revision : "AB", "testRecipeID":86, value : 26.63, treatment : 'Pressure' },
{model_num : "ABC", revision : "AB", "testRecipeID":86, value : 26.00, treatment : 'Gas' },
{model_num : "ABC", revision : "AB", "testRecipeID":86, value : 23.75, treatment : 'Temp' }
];
and i would like to end up with something like this:
var data=[{model_num : "ABC", revision : "AA", "testRecipeID":85, "Pressure":31.25, "Gas":31.25, "Temp": 33.12,"Current":25.87 },{model_num : "ABC", revision : "AB", "testRecipeID":86, "Gas":26.00,"Temp":23.75}]
I know how to do this in JS but not on PHP and it turns out I need to do it in my php so that I can process the large amounts of data that I have. I have this based on another question that I found but It doesn't work it returns a 0
$table = array();
$round_names = array();
$total = array();
foreach ($data as $score)
{
$round_names[] = $score->treatment;
$table[$score->testRecipeID][$score->treatment] = $score->value;
$total[$score->testRecipeID] += $score->value;
print_r($round_names);
}
$round_names = array_unique($round_names);
foreach ($table as $player => $rounds)
{
echo "$player\t";
foreach ($round_names as $round)
echo "$rounds[$round]\t";
echo "$total[$player]\n";
}
any help will be greatly appreciated!
this is how i do it in JS
var result = [];
data.forEach(function(e) {
var a = e.model_num + '|' + e.revision+ '|'e.recipeID;
if(!this[a]) {
this[a] = {model_num: e.model_num, revision: e.revision, recipeID: e.recipeID}
result.push(this[a]);
}
this[a][e.treatment] = e.value;
}, {});
If you need the structure, you can try with JSON Encode:
<?php
$arr = array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5);
echo json_encode($arr);
?>
Which outputs:
{"a":1,"b":2,"c":3,"d":4,"e":5}
If you need it to be an array, use:
echo json_encode(array($arr));
This is your JS function in PHP
<?php
$data = '[
{"model_num" : "ABC", "revision" : "AA", "testRecipeID":85, "value" : 31.25, "treatment" : "Pressure" },
{"model_num" : "ABC", "revision" : "AA", "testRecipeID":85, "value" : 31.25, "treatment" : "Gas" },
{"model_num" : "ABC", "revision" : "AA", "testRecipeID":85, "value" : 33.12, "treatment" : "Temp" },
{"model_num" : "ABC", "revision" : "AA", "testRecipeID":85, "value" : 25.87, "treatment" : "Current" },
{"model_num" : "ABC", "revision" : "AB", "testRecipeID":86, "value" : 26.63, "treatment" : "Pressure" },
{"model_num" : "ABC", "revision" : "AB", "testRecipeID":86, "value" : 26.00, "treatment" : "Gas" },
{"model_num" : "ABC", "revision" : "AB", "testRecipeID":86, "value" : 23.75, "treatment" : "Temp" }
]';
$data = json_decode($data);
$result = [];
foreach ($data as $row) {
$a = $row->model_num . '|' . $row->revision . '|' . $row->testRecipeID;
if (! array_key_exists($a, $result)) {
$result[$a] = [
'model_num' => $row->model_num,
'revision' => $row->revision,
'testRecipeID' => $row->testRecipeID
];
}
}
Because neither of the previous answers managed to provide the desired output, I am compelled to answer this pivot question.
Create a temporary string from the 3 identifying column values.
Use that string as the first level key while grouping related row data.
The null coalescing assignment operator spares you needing to call isset() to check if the group has been encountered before. If it is the first encounter, save the three core elements to the row.
Unconditionally push the dynamically keyed value into the group's row.
5 When finished iterating clear away the temporary keys with array_values().
Code: (Demo)
$data = [
['model_num' => 'ABC', 'revision' => 'AA', 'testRecipeID' => 85, 'value' => 31.25, 'treatment' => 'Pressure'],
['model_num' => 'ABC', 'revision' => 'AA', 'testRecipeID' => 85, 'value' => 31.25, 'treatment' => 'Gas'],
['model_num' => 'ABC', 'revision' => 'AA', 'testRecipeID' => 85, 'value' => 33.12, 'treatment' => 'Temp'],
['model_num' => 'ABC', 'revision' => 'AA', 'testRecipeID' => 85, 'value' => 25.87, 'treatment' => 'Current'],
['model_num' => 'ABC', 'revision' => 'AB', 'testRecipeID' => 86, 'value' => 26.63, 'treatment' => 'Pressure'],
['model_num' => 'ABC', 'revision' => 'AB', 'testRecipeID' => 86, 'value' => 26.0, 'treatment' => 'Gas'],
['model_num' => 'ABC', 'revision' => 'AB', 'testRecipeID' => 86, 'value' => 23.75, 'treatment' => 'Temp']
];
$result = [];
foreach ($data as $row) {
$compositeKey = "{$row['model_num']}-{$row['revision']}-{$row['testRecipeID']}";
$result[$compositeKey] ??= [
'model_num' => $row['model_num'],
'revision' => $row['revision'],
'testRecipeID' => $row['testRecipeID']
];
$result[$compositeKey][$row['treatment']] = $row['value'];
}
var_export(array_values($result));
Output:
array (
0 =>
array (
'model_num' => 'ABC',
'revision' => 'AA',
'testRecipeID' => 85,
'Pressure' => 31.25,
'Gas' => 31.25,
'Temp' => 33.12,
'Current' => 25.87,
),
1 =>
array (
'model_num' => 'ABC',
'revision' => 'AB',
'testRecipeID' => 86,
'Pressure' => 26.63,
'Gas' => 26.0,
'Temp' => 23.75,
),
)

Nested json string with variables in php

I will not lie to you guys: i hate nested Arrays.
So instead of my [working] code below
$query_array = Array(
"fields" => Array ("timestamp", "user.raw", "mailbox", "verb", "backend"),
"size" => 1,
"sort" => Array (Array ("#timestamp" => Array ("order" => $varOrder))),
"query" => Array (
"bool" => Array (
"must" => Array (
Array ("match" => Array ("verb" => "open")),
Array ("term" => Array ($varField => $varValue))
)
)
)
);
I want to use the [non working] code below
$query_json_string = '{
"fields" : [ "timestamp", "user.raw", "mailbox", "verb", "backend" ],
"size" : 1,
"sort" : [ { "#timestamp" : { "order" : $varOrder } } ],
"query" : {
"bool": {
"must": [
{ "match" : { "verb" : "open" } },
{ "term" : { $varField : $varValue } }
]
}
}
}';
So much easier to maintain...
But inside the single quotes the variables varOrder, varField and varValue are never expanded (i believe).
How can i use variables inside this nice and clean json string?
As I understand your question, you're simply not happy with php's array syntax. If that's the case, then don't use the old syntax. Instead use simple square brackets, which are available since version 5.4.
Doing so, your final code could be:
$query = [
"fields" => ["timestamp", "user.raw", "mailbox", "verb", "backend"],
"size" => 1,
"sort" => [["#timestamp" => ["order" => $varOrder]]],
"query" => [
"bool" => [
"must" => [
["match" => ["verb" => "open"]],
["term" => [$varField => $varValue]]
]
]
]
];
Which issn't far off your desired syntax. The only difference being => instead of :. But with the added benefit of not needing to declare objects with {}.
JSON is basically Javascript's version of a associative array.
$query_array = Array(
"fields" => Array ("timestamp", "user.raw", "mailbox", "verb", "backend"),
"size" => 1,
"sort" => Array (Array ("#timestamp" => Array ("order" => $varOrder))),
"query" => Array (
"bool" => Array (
"must" => Array (
Array ("match" => Array ("verb" => "open")),
Array ("term" => Array ($varField => $varValue))
)
)
)
);
$query_json_string = json_encode($query_array);
But if you have a problem with that, heredoc syntax should work for you..
$query_json_string = <<<JSON
{
"fields" : [ "timestamp", "user.raw", "mailbox", "verb", "backend" ],
"size" : 1,
"sort" : [ { "#timestamp" : { "order" : $varOrder } } ],
"query" : {
"bool": {
"must": [
{ "match" : { "verb" : "open" } },
{ "term" : { $varField : $varValue } }
]
}
}
}
JSON;
// if you wanted PHP array,
$query_array = json_decode($query_json_string, true);
You can switch the from single to double quotes and escape your double qoutes
$query_json_string = "{
\"fields\" : [ \"timestamp\", \"user.raw\", \"mailbox\", \"verb\", \"backend\" ],
\"size\" : 1,
\"sort\" : [ { \"#timestamp\" : { \"order\" : $varOrder } } ],
\"query\" : {
\"bool\": {
\"must\": [
{ \"match\" : { \"verb\" : \"open\" } },
{ \"term\" : { $varField : $varValue } }
]
}
}
}";

mongodb avg in aggregation of array element

I have the following collection structure
{
"_id": {
"d_timestamp": NumberLong(1429949699),
"d_isostamp": ISODate("2015-04-25T08:14:59.0Z")
},
"XBT-USD-cpx-okc": [
{
"buySpread": -1.80081
}
I run the following aggregation
$spreadName ='XBT-USD-stp-nex';
$pipe = array(
array(
'$match' => array(
'_id.d_isostamp' => array(
'$gt' => $start, '$lt' => $end
)
)
),
array(
'$project' => array(
'sellSpread' =>'$'.$spreadName.'.sellSpread',
)
),
array(
'$group' => array(
'_id' => array(
'isodate' => array(
'$minute' => '$_id.d_isostamp'
)
),
'rsell_spread' => array(
'$avg' => '$sellSpread'
),
)
),
);
$out = $collection->aggregate($pipe ,$options);
and I get as a result the value 0 for rsell_spread whereas if I run a $max for instance instead of an $avg in the $group , I get an accurate value for rsell_spread , w/ the following structure
{
"_id": {
"isodate": ISODate("2015-04-25T08:00:58.0Z")
},
"rsell_spread": [
-4.49996▼
]
}
So I have two questions :
1/ How come does the $avg function does not work?
2/ How can I can a result not in an array when I use $max for example (just a regular number)?
The $avg group accumulator operator does work, it's only that in your case it is being applied to an element in an array and thus gives the "incorrect" result.
When you use the $max group accumulator operator, it returns the the highest value that results from applying an expression to each document in a group of documents, thus in your example it returned the maximum array.
To demonstrate this, consider adding a few sample documents to a test collection in mongoshell:
db.test.insert([
{
"_id" : {
"d_timestamp" : NumberLong(1429949699),
"d_isostamp" : ISODate("2015-04-25T08:14:59.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80081
}
]
},
{
"_id" : {
"d_timestamp" : NumberLong(1429949710),
"d_isostamp" : ISODate("2015-04-25T08:15:10.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80079
}
]
},
{
"_id" : {
"d_timestamp" : NumberLong(1429949720),
"d_isostamp" : ISODate("2015-04-25T08:15:20.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80083
}
]
},
{
"_id" : {
"d_timestamp" : NumberLong(1429949730),
"d_isostamp" : ISODate("2015-04-25T08:15:30.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80087
}
]
}
])
Now, replicating the same operation above in mongoshell:
var spreadName = "XBT-USD-stp-nex",
start = new Date(2015, 3, 25),
end = new Date(2015, 3, 26);
db.test.aggregate([
{
"$match": {
"_id.d_isostamp": { "$gte": start, "$lte": end }
}
},
{
"$project": {
"sellSpread": "$"+spreadName+".sellSpread"
}
}/*,<--- deliberately omitted the $unwind stage from the pipeline to replicate the current pipeline
{
"$unwind": "$sellSpread"
}*/,
{
"$group": {
"_id": {
"isodate": { "$minute": "$_id.d_isostamp"}
},
"rsell_spread": {
"$avg": "$sellSpread"
}
}
}
])
Output:
/* 0 */
{
"result" : [
{
"_id" : {
"isodate" : 15
},
"rsell_spread" : 0
},
{
"_id" : {
"isodate" : 14
},
"rsell_spread" : 0
}
],
"ok" : 1
}
The solution is to include an $unwind operator pipeline stage after the $project step, this will deconstruct the XBT-USD-stp-nex array field from the input documents and outputs a document for each element. Each output document replaces the array with an element value. This will then make it possible for the $avg group accumulator operator to work.
Including this will give the aggregation result:
/* 0 */
{
"result" : [
{
"_id" : {
"isodate" : 15
},
"rsell_spread" : -1.80083
},
{
"_id" : {
"isodate" : 14
},
"rsell_spread" : -1.80081
}
],
"ok" : 1
}
So your final working aggregation in PHP should be:
$spreadName ='XBT-USD-stp-nex';
$pipe = array(
array(
'$match' => array(
'_id.d_isostamp' => array(
'$gt' => $start, '$lt' => $end
)
)
),
array(
'$project' => array(
'sellSpread' =>'$'.$spreadName.'.sellSpread',
)
),
array('$unwind' => '$sellSpread'),
array(
'$group' => array(
'_id' => array(
'isodate' => array(
'$minute' => '$_id.d_isostamp'
)
),
'rsell_spread' => array(
'$avg' => '$sellSpread'
),
)
),
);
$out = $collection->aggregate($pipe ,$options);

Using mongodb's $and in php

Setup, php + mongodb.
The database should contain multiple items where 'Title' contains both 'Apple' and 'Orange'. I am trying to obtain only the items that contain BOTH 'Apple' and 'Orange'.
$regexOne = new MongoRegex( '/Apple/i' );
$regexTwo = new MongoRegex('/Orange/i');
$cursor = $collection->find( array( '$and' => array( array('Title' => $regexOne), array('Title' => $regexTwo) ) ) );
The same query with the '$and' replaced with a '$or' works correctly (or at least appears to), but this returns nothing despite there being data in the database that matches these conditions.
Echoing what dimo414 said, the first step is simply comparing Mongo's shell to PHP. Both of these are equivalent:
$ mongo
MongoDB shell version: 2.2.0-rc0
connecting to: test
> db.foo.drop()
true
> db.foo.insert({title:"apple"})
> db.foo.insert({title:"orange"})
> db.foo.insert({title:"apple orange"})
> db.foo.insert({title:"banana apple"})
> db.foo.insert({title:"banana plum"})
> db.foo.find({$and: [{title:/apple/i}, {title:/orange/i}]})
{ "_id" : ObjectId("501804c4e7dc9abd7b58cd83"), "title" : "apple orange" }
> db.foo.find({$or: [{title:/apple/i}, {title:/orange/i}]})
{ "_id" : ObjectId("501804bde7dc9abd7b58cd81"), "title" : "apple" }
{ "_id" : ObjectId("501804c0e7dc9abd7b58cd82"), "title" : "orange" }
{ "_id" : ObjectId("501804c4e7dc9abd7b58cd83"), "title" : "apple orange" }
{ "_id" : ObjectId("501804cce7dc9abd7b58cd84"), "title" : "banana apple" }
And in PHP (using phpsh):
php> $c = (new Mongo())->test->foo;
php> =iterator_to_array($c->find(['$and' => [['title' => new MongoRegex('/apple/i')], ['title' => new MongoRegex('/orange/i')]]]))
array(
"501804c4e7dc9abd7b58cd83" => array(
"_id" => <object #10 of type MongoId> {
$id => "501804c4e7dc9abd7b58cd83",
},
"title" => "apple orange",
),
)
php> =iterator_to_array($c->find(['$or' => [['title' => new MongoRegex('/apple/i')], ['title' => new MongoRegex('/orange/i')]]]))
array(
"501804bde7dc9abd7b58cd81" => array(
"_id" => <object #11 of type MongoId> {
$id => "501804bde7dc9abd7b58cd81",
},
"title" => "apple",
),
"501804c0e7dc9abd7b58cd82" => array(
"_id" => <object #12 of type MongoId> {
$id => "501804c0e7dc9abd7b58cd82",
},
"title" => "orange",
),
"501804c4e7dc9abd7b58cd83" => array(
"_id" => <object #13 of type MongoId> {
$id => "501804c4e7dc9abd7b58cd83",
},
"title" => "apple orange",
),
"501804cce7dc9abd7b58cd84" => array(
"_id" => <object #14 of type MongoId> {
$id => "501804cce7dc9abd7b58cd84",
},
"title" => "banana apple",
),
)

Categories