select value that occurs most frequently by group

2019-07-25 00:35发布

问题:

I have RDF data about hospital patients, including their date of birth. There are frequently multiple triples abut their date of birth, and some of those triples may be wrong. My group has decided to use this rule: whatever date occurs most frequently will provisionally be considered correct. It's clear how to do this in any programming language of our choice, external to SPARQL.

Is an aggregation of aggregations possible in SPARQL?

I have read the similar question SPARQL selecting MAX value of a counter, but I'm not there yet.


Given these triples:

@prefix turbo: <http://example.org/ontologies/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<http://example.org/ontologies/b6be95364ec943af2ef4ab161c11c855>
  a <http://example.org/ontologies/StudyPartWithBBDonation> ;
  turbo:hasBirthDateO turbo:3950b2b6-f575-4074-b0e8-f9fa3378f3be, turbo:4250aafa-4b0c-4f73-92b6-7639f427b61d, turbo:a3e6676e-a214-4af4-b8ef-34a8e20170bf .

turbo:3950b2b6-f575-4074-b0e8-f9fa3378f3be turbo:hasDateValue "1971-12-30"^^xsd:date .
turbo:4250aafa-4b0c-4f73-92b6-7639f427b61d turbo:hasDateValue "1971-12-30"^^xsd:date .
turbo:a3e6676e-a214-4af4-b8ef-34a8e20170bf turbo:hasDateValue "1971-12-30"^^xsd:date .
turbo:6e200ca0d5150282787464a2bda55814
  a turbo:StudyPartWithBBDonation ;
  turbo:hasBirthDateO turbo:b09519f5-b123-40d5-bb4a-737ec9f8b9a8, turbo:06c56881-a6c7-4d1d-993b-add8862dffd7, turbo:12ef184d-c8d6-4d93-a558-a3ba47bb56ca .

turbo:b09519f5-b123-40d5-bb4a-737ec9f8b9a8 turbo:hasDateValue "2000-04-04"^^xsd:date .
turbo:06c56881-a6c7-4d1d-993b-add8862dffd7 turbo:hasDateValue "2000-04-04"^^xsd:date .
turbo:12ef184d-c8d6-4d93-a558-a3ba47bb56ca turbo:hasDateValue "2000-04-05"^^xsd:date .

This query

PREFIX  turbo: <http://example.org/ontologies/>
SELECT  ?part ?xsddate (COUNT(?xsddate) AS ?datecount) 
  { ?part  rdf:type             turbo:StudyPartWithBBDonation ;
           turbo:hasBirthDateO  ?dob .
    ?dob   turbo:hasDateValue   ?xsddate
  }
GROUP BY ?part ?xsddate

gives the following:

+----------------------------------------+------------------------+------------------+
|                  part                  |        xsddate         |    datecount     |
+----------------------------------------+------------------------+------------------+
| turbo:6e200ca0d5150282787464a2bda55814 | "2000-04-05"^^xsd:date | "1"^^xsd:integer |
| turbo:b6be95364ec943af2ef4ab161c11c855 | "1971-12-30"^^xsd:date | "3"^^xsd:integer |
| turbo:6e200ca0d5150282787464a2bda55814 | "2000-04-04"^^xsd:date | "2"^^xsd:integer |
+----------------------------------------+------------------------+------------------+

I only want to see the date with the highest count for each patient who is participating in a study:

+----------------------------------------+------------------------+------------------+
|                  part                  |        xsddate         |    datecount     |
+----------------------------------------+------------------------+------------------+
| turbo:b6be95364ec943af2ef4ab161c11c855 | "1971-12-30"^^xsd:date | "3"^^xsd:integer |
| turbo:6e200ca0d5150282787464a2bda55814 | "2000-04-04"^^xsd:date | "2"^^xsd:integer |
+----------------------------------------+------------------------+------------------+

I think I'm getting close here. Now I need to get the counts and max counts on the same row!

PREFIX  rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX  turbo: <http://example.org/ontologies/>

SELECT  ?part ?xsddate ?datecount ?countmax
WHERE
  {   { SELECT  ?part ?xsddate (COUNT(?xsddate) AS ?datecount)
        WHERE
          { ?part  rdf:type             turbo:StudyPartWithBBDonation ;
                   turbo:hasBirthDateO  ?dob .
            ?dob   turbo:hasDateValue   ?xsddate
          }
        GROUP BY ?part ?xsddate
      }
    UNION
      { SELECT  ?part (MAX(?datecount) AS ?countmax)
        WHERE
          { SELECT  ?part ?xsddate (COUNT(?xsddate) AS ?datecount)
            WHERE
              { ?part  rdf:type             turbo:StudyPartWithBBDonation ;
                       turbo:hasBirthDateO  ?dob .
                ?dob   turbo:hasDateValue   ?xsddate
              }
            GROUP BY ?part ?xsddate
          }
        GROUP BY ?part
      }
  }

giving

+----------------------------------------+------------------------+------------------+------------------+
|                  part                  |        xsddate         |    datecount     |     countmax     |
+----------------------------------------+------------------------+------------------+------------------+
| turbo:6e200ca0d5150282787464a2bda55814 | "2000-04-05"^^xsd:date | "1"^^xsd:integer |                  |
| turbo:b6be95364ec943af2ef4ab161c11c855 | "1971-12-30"^^xsd:date | "3"^^xsd:integer |                  |
| turbo:6e200ca0d5150282787464a2bda55814 | "2000-04-04"^^xsd:date | "2"^^xsd:integer |                  |
| turbo:6e200ca0d5150282787464a2bda55814 |                        |                  | "2"^^xsd:integer |
| turbo:b6be95364ec943af2ef4ab161c11c855 |                        |                  | "3"^^xsd:integer |
+----------------------------------------+------------------------+------------------+------------------+

回答1:

Essentially, you need just to replace UNION with . in your query (or you could just remove this UNION, as @AKSW has pointed out in the comment below).

In GraphDB, however, your will recieve an error:

Variable ?datecount is already used in a previous projection. Bindings are not propagated through projections since Sesame 2.8, so this may lead to logical errors in the query.

Thus, change your query in this way:

PREFIX  rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX  turbo: <http://example.org/ontologies/>

SELECT  ?part ?xsddate ?datecount_ ?countmax
WHERE
  {   { SELECT  ?part ?xsddate (COUNT(?xsddate) AS ?datecount_)
        WHERE
          { ?part  rdf:type             turbo:StudyPartWithBBDonation ;
                   turbo:hasBirthDateO  ?dob .
            ?dob   turbo:hasDateValue   ?xsddate
          }
        GROUP BY ?part ?xsddate
      }
      .
      { SELECT  ?part (MAX(?datecount) AS ?countmax)
        WHERE
          { SELECT  ?part ?xsddate (COUNT(?xsddate) AS ?datecount)
            WHERE
              { ?part  rdf:type             turbo:StudyPartWithBBDonation ;
                       turbo:hasBirthDateO  ?dob .
                ?dob   turbo:hasDateValue   ?xsddate
              }
            GROUP BY ?part ?xsddate
          }
        GROUP BY ?part
      }
  }

In Blazegraph, you could use named subqueries:

PREFIX  rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX  turbo: <http://example.org/ontologies/>

SELECT  ?part ?xsddate ?datecount ?countmax

WITH 
  { SELECT  ?part ?xsddate (COUNT(?xsddate) AS ?datecount)
      WHERE
        { ?part  rdf:type             turbo:StudyPartWithBBDonation ;
                 turbo:hasBirthDateO  ?dob .
          ?dob   turbo:hasDateValue   ?xsddate
        }
       GROUP BY ?part ?xsddate
  } AS %sub

WHERE
  {  { SELECT ?part (MAX(?datecount) AS ?countmax)
       WHERE { INCLUDE %sub } GROUP BY ?part
     }
      INCLUDE %sub
  }


回答2:

My elaboration on Stanislav's awesome answer

  • renamed the ?datecount in one of the {} patterns
  • added a filter
  • inserting the consensus DOB into a named graph within the triplestore

.

PREFIX  rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX  turbo: <http://example.org/ontologies/>

INSERT {
  GRAPH turbo:DOB_conclusions {
    ?part turbo:hasBirthDateO ?DOBconc .
    ?DOBconc turbo:hasDateValue ?xsddate .
    ?DOBconc turbo:conclusionated true .
    ?DOBconc rdf:type <http://www.ebi.ac.uk/efo/EFO_0004950> .
  }
}
WHERE
  { { SELECT  ?part ?xsddate (COUNT(?xsddate) AS ?datecount)
      WHERE
        { ?part  rdf:type             turbo:StudyPartWithBBDonation ;
                 turbo:hasBirthDateO  ?dob .
          ?dob   turbo:hasDateValue   ?xsddate
        }
      GROUP BY ?part ?xsddate
    }
    .
    { SELECT  ?part (MAX(?datecount2) AS ?countmax)
      WHERE
        { SELECT  ?part ?xsddate (COUNT(?xsddate) AS ?datecount2)
          WHERE
            { ?part  rdf:type             turbo:StudyPartWithBBDonation ;
                     turbo:hasBirthDateO  ?dob .
              ?dob   turbo:hasDateValue   ?xsddate
            }
          GROUP BY ?part ?xsddate
        }
      GROUP BY ?part
    }
    FILTER ( ?datecount = ?countmax )
    BIND(uri(concat("http://transformunify.org/ontologies/", struuid())) AS ?DOBconc)
  }


标签: sparql