Consuming Senpy services ======================== This short tutorial will teach you how to consume services in several ways, taking advantage of the features of the framework. In particular, we will cover: - Annotating text with sentiment - Annotating text with emotion - Getting results in different formats (Turtle, XML, text…) - Asking for specific emotion models (automatic model conversion) - Listing available services in an endpoint - Switching to different services - Calling multiple services in the same request (Pipelines) The latest version of this IPython notebook is available at: https://github.com/gsi-upm/senpy/tree/master/docs/Quickstart.ipynb Requirements ------------ For the sake of simplicity, this tutorial will use the demo server: http://senpy.gsi.upm.es: .. code:: ipython3 endpoint = 'http://senpy.gsi.upm.es/api' This server runs some open source plugins for sentiment and emotion analysis. The HTTP API of Senpy can be queried with your favourite tool. This is just an example using curl: .. code:: bash curl "http://senpy.gsi.upm.es/api/sentiment140" --data-urlencode "input=Senpy is awesome" For simplicity, in this tutorial we will use the requests library. We will also add a function to add syntax highlighting for the JSON-LD/Turtle results: .. code:: ipython3 try: from IPython.display import Code def pretty(txt, language='json-ld'): return Code(txt, language=language) except ImportError: def pretty(txt, **kwargs): print(txt) Once you’re familiar with Senpy, you can deploy your own instance quite easily. e.g. using docker: :: docker run -ti --name 'SenpyEndpoint' -d -p 5000:5000 gsiupm/senpy Then, feel free to change the endpoint variable to run the examples in your own instance. Sentiment Analysis of Text -------------------------- To start, let us analyse the sentiment in the following sentence: *senpy is a wonderful service*. For now, we will use the `sentiment140 `__ service, through the sentiment140 plugin. We will later cover how to use a different service. .. code:: ipython3 import requests res = requests.get(f'{endpoint}/sentiment140', params={"input": "Senpy is awesome",}) pretty(res.text) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL3NlbnRpbWVudDE0MD9pbnB1dD1TZW5weStpcythd2Vzb21lIw%3D%3D",
      "@type": "Results",
      "entries": [
        {
          "@id": "prefix:",
          "@type": "Entry",
          "marl:hasOpinion": [
            {
              "@type": "Sentiment",
              "marl:hasPolarity": "marl:Positive",
              "prov:wasGeneratedBy": "prefix:Analysis_1554364667.7955277"
            }
          ],
          "nif:isString": "Senpy is awesome",
          "onyx:hasEmotionSet": []
        }
      ]
    }
    
Senpy services always return an object of type ``senpy:Results``, with a list of entries. You can think of an entry as a self-contained textual context (``nif:Context`` and ``senpy:Entry``). Entries can be as short as a sentence, or as long as a news article. Each entry has a ``nif:isString`` property that contains the original text of the entry, and several other properties that are provided by the plugins. For instance, sentiment annotations are provided through ``marl:hasOpinion``. The annotations are semantic. We can ask Senpy for the expanded JSON-LD output to reveal the full URIs of each property and entity: .. code:: ipython3 import requests res = requests.get(f'{endpoint}/sentiment140', params={"input": "Senpy is awesome", "expanded": True}) pretty(res.text) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL3NlbnRpbWVudDE0MD9pbnB1dD1TZW5weStpcythd2Vzb21lJmV4cGFuZGVkPVRydWUj",
      "@type": [
        "http://www.gsi.upm.es/onto/senpy/ns#Results"
      ],
      "http://www.w3.org/ns/prov#used": [
        {
          "@id": "http://senpy.invalid/",
          "@type": [
            "http://www.gsi.upm.es/onto/senpy/ns#Entry"
          ],
          "http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#isString": [
            {
              "@value": "Senpy is awesome"
            }
          ],
          "http://www.gsi.dit.upm.es/ontologies/marl/ns#hasOpinion": [
            {
              "@type": [
                "http://www.gsi.upm.es/onto/senpy/ns#Sentiment"
              ],
              "http://www.gsi.dit.upm.es/ontologies/marl/ns#hasPolarity": [
                {
                  "@value": "marl:Positive"
                }
              ],
              "http://www.w3.org/ns/prov#wasGeneratedBy": [
                {
                  "@id": "http://senpy.invalid/Analysis_1554364668.1011338"
                }
              ]
            }
          ],
          "http://www.gsi.dit.upm.es/ontologies/onyx/ns#hasEmotionSet": []
        }
      ]
    }
    
.. code:: ipython3 pretty(res.text) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL3NlbnRpbWVudDE0MD9pbnB1dD1TZW5weStpcythd2Vzb21lJmV4cGFuZGVkPVRydWUj",
      "@type": [
        "http://www.gsi.upm.es/onto/senpy/ns#Results"
      ],
      "http://www.w3.org/ns/prov#used": [
        {
          "@id": "http://senpy.invalid/",
          "@type": [
            "http://www.gsi.upm.es/onto/senpy/ns#Entry"
          ],
          "http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#isString": [
            {
              "@value": "Senpy is awesome"
            }
          ],
          "http://www.gsi.dit.upm.es/ontologies/marl/ns#hasOpinion": [
            {
              "@type": [
                "http://www.gsi.upm.es/onto/senpy/ns#Sentiment"
              ],
              "http://www.gsi.dit.upm.es/ontologies/marl/ns#hasPolarity": [
                {
                  "@value": "marl:Positive"
                }
              ],
              "http://www.w3.org/ns/prov#wasGeneratedBy": [
                {
                  "@id": "http://senpy.invalid/Analysis_1554364668.1011338"
                }
              ]
            }
          ],
          "http://www.gsi.dit.upm.es/ontologies/onyx/ns#hasEmotionSet": []
        }
      ]
    }
    
Other output formats -------------------- Senpy supports several semantic formats, like turtle and xml-RDF. You can select the format of the output with the ``outformat`` parameter: .. code:: ipython3 res = requests.get(f'{endpoint}/sentiment140', params={"input": "Senpy is the best framework for semantic sentiment analysis, and very easy to use", "outformat": "turtle"}) pretty(res.text, language='turtle') .. raw:: html
@prefix : <http://www.gsi.upm.es/onto/senpy/ns#> .
    @prefix dc: <http://dublincore.org/2012/06/14/dcelements#> .
    @prefix emoml: <http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/emotionml/ns#> .
    @prefix endpoint: <http://senpy.gsi.upm.es/api/> .
    @prefix fam: <http://vocab.fusepool.info/fam#> .
    @prefix marl: <http://www.gsi.dit.upm.es/ontologies/marl/ns#> .
    @prefix nif: <http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#> .
    @prefix onyx: <http://www.gsi.dit.upm.es/ontologies/onyx/ns#> .
    @prefix prefix: <http://senpy.invalid/> .
    @prefix prov: <http://www.w3.org/ns/prov#> .
    @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
    @prefix senpy: <http://www.gsi.upm.es/onto/senpy/ns#> .
    @prefix wna: <http://www.gsi.dit.upm.es/ontologies/wnaffect/ns#> .
    @prefix xml: <http://www.w3.org/XML/1998/namespace> .
    @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
    
    prefix: a senpy:Entry ;
        nif:isString "Senpy is the best framework for semantic sentiment analysis, and very easy to use" ;
        marl:hasOpinion [ a senpy:Sentiment ;
                marl:hasPolarity "marl:Positive" ;
                prov:wasGeneratedBy prefix:Analysis_1554364668.5153766 ] .
    
    [] a senpy:Results ;
        prov:used prefix: .
    
Selecting fields from the output -------------------------------- The full output in the previous sections is very useful because it is semantically annotated. However, it is also quite verbose if we only want to label a piece of text, or get a polarity value. For such simple cases, the API has a special ``fields`` method you can use to get a specific field from the results, and even transform the results. Senpy uses jmespath under the hood, which has its own notation. To illustrate this, let us get only the text (``nif:isString``) from each entry: .. code:: ipython3 res = requests.get(f'{endpoint}/sentiment140', params={"input": "Senpy is a wonderful service", "fields": 'entries[]."nif:isString"'}) print(res.text) .. parsed-literal:: ["Senpy is a wonderful service"] Or we could get both the text and the polarity of the text (assuming there is only one opinion per entry) with a slightly more complicated query: .. code:: ipython3 res = requests.get(f'{endpoint}/sentiment140', params={"input": "Senpy is a service. Wonderful service.", "delimiter": "sentence", "fields": 'entries[0].["nif:isString", "marl:hasOpinion"[0]."marl:hasPolarity"]'}) print(res.text) .. parsed-literal:: ["Senpy is a service. Wonderful service.", "marl:Neutral"] jmespath is rather extensive for this tutorial. We will cover only the most simple cases, so you do not need to learn much about the notation. For more complicated transformations, check out `jmespath `__. In addition to a fairly complete documentation, they have a live environment you can use to test your queries. Emotion analysis ---------------- Senpy uses the ``onyx`` vocabulary to represent emotions, which incorporates the notion of ``EmotionSet``\ ’s, an emotion that is composed of several emotions. In a nutshell, an ``Entry`` is linked to one or more ``EmotionSet``, which in turn is made up of one or more ``Emotion``. Let’s illustrate it with an example, using the ``emotion-depechemood`` plugin. .. code:: ipython3 res = requests.get(f'{endpoint}/emotion-depechemood', params={"input": "Senpy is a wonderful that service"}) pretty(res.text) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL2Vtb3Rpb24tZGVwZWNoZW1vb2Q_aW5wdXQ9U2VucHkraXMrYSt3b25kZXJmdWwrdGhhdCtzZXJ2aWNlIw%3D%3D",
      "@type": "Results",
      "entries": [
        {
          "@id": "prefix:",
          "@type": "Entry",
          "marl:hasOpinion": [],
          "nif:isString": "Senpy is a wonderful that service",
          "onyx:hasEmotionSet": [
            {
              "@type": "EmotionSet",
              "onyx:hasEmotion": [
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:negative-fear",
                  "onyx:hasEmotionIntensity": 0.06258366271018097
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:amusement",
                  "onyx:hasEmotionIntensity": 0.15784834034155437
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:anger",
                  "onyx:hasEmotionIntensity": 0.08728815135373413
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:annoyance",
                  "onyx:hasEmotionIntensity": 0.12184635680460143
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:indifference",
                  "onyx:hasEmotionIntensity": 0.1374081151031531
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:joy",
                  "onyx:hasEmotionIntensity": 0.12267040802346799
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:awe",
                  "onyx:hasEmotionIntensity": 0.21085262130713067
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:sadness",
                  "onyx:hasEmotionIntensity": 0.09950234435617733
                }
              ],
              "prov:wasGeneratedBy": "prefix:Analysis_1554364674.7078097"
            }
          ]
        }
      ]
    }
    
As you have probably noticed, there are several emotions in this result, each with a different intensity. We can also tell senpy to only return the emotion with the maximum intensity using the ``maxemotion`` parameter: .. code:: ipython3 res = requests.get(f'{endpoint}/emotion-depechemood', params={"input": "Senpy is a wonderful service", "maxemotion": True}) pretty(res.text) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL2Vtb3Rpb24tZGVwZWNoZW1vb2Q_aW5wdXQ9U2VucHkraXMrYSt3b25kZXJmdWwrc2VydmljZSZtYXhlbW90aW9uPVRydWUj",
      "@type": "Results",
      "entries": [
        {
          "@id": "prefix:",
          "@type": "Entry",
          "marl:hasOpinion": [],
          "nif:isString": "Senpy is a wonderful service",
          "onyx:hasEmotionSet": [
            {
              "@type": "EmotionSet",
              "onyx:hasEmotion": [
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:awe",
                  "onyx:hasEmotionIntensity": 0.21085262130713067
                }
              ],
              "prov:wasGeneratedBy": "prefix:Analysis_1554364674.8374224"
            }
          ]
        }
      ]
    }
    
We can combine this feature with the ``fields`` parameter to get only the label and the intensity: .. code:: ipython3 res = requests.get(f'{endpoint}/emotion-depechemood', params={"input": "Senpy is a wonderful service", "fields": 'entries[]."onyx:hasEmotionSet"[]."onyx:hasEmotion"[]["onyx:hasEmotionCategory","onyx:hasEmotionIntensity"]', "maxemotion": True}) pretty(res.text) .. raw:: html
[["wna:awe", 0.21085262130713067]]
    
Emotion conversion ------------------ If the model used by a plugin is not right for your application, you can ask for a specific emotion model in your request. Senpy ships with emotion conversion capabilities, and it will try to automatically convert the results. For example, the ``emotion-anew`` plugin uses the dimensional ``pad`` (or VAD, valence-arousal-dominance) model, as we can see here: .. code:: ipython3 res = requests.get(f'{endpoint}/emotion-anew', params={"input": "Senpy is a wonderful service and I love it"}) print(res.text) .. parsed-literal:: { "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL2Vtb3Rpb24tYW5ldz9pbnB1dD1TZW5weStpcythK3dvbmRlcmZ1bCtzZXJ2aWNlK2FuZCtJK2xvdmUraXQj", "@type": "Results", "entries": [ { "@id": "prefix:", "@type": "Entry", "marl:hasOpinion": [], "nif:isString": "Senpy is a wonderful service and I love it", "onyx:hasEmotionSet": [ { "@id": "Emotions0", "@type": "EmotionSet", "onyx:hasEmotion": [ { "@id": "Emotion0", "@type": "Emotion", "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#arousal": 6.44, "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#dominance": 7.11, "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#valence": 8.72, "prov:wasGeneratedBy": "prefix:Analysis_1554364675.1427004" } ], "prov:wasGeneratedBy": "prefix:Analysis_1554364675.1427004" } ] } ] } If we need a category level, we can ask for the equivalent results in the ``big6`` model: .. code:: ipython3 res = requests.get(f'{endpoint}/emotion-anew', params={"input": "Senpy is a wonderful service and I love it", "emotion-model": "emoml:big6"}) pretty(res.text) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL2Vtb3Rpb24tYW5ldz9pbnB1dD1TZW5weStpcythK3dvbmRlcmZ1bCtzZXJ2aWNlK2FuZCtJK2xvdmUraXQmZW1vdGlvbi1tb2RlbD1lbW9tbCUzQWJpZzYj",
      "@type": "Results",
      "entries": [
        {
          "@id": "prefix:",
          "@type": "Entry",
          "marl:hasOpinion": [],
          "nif:isString": "Senpy is a wonderful service and I love it",
          "onyx:hasEmotionSet": [
            {
              "@id": "Emotions0",
              "@type": "EmotionSet",
              "onyx:hasEmotion": [
                {
                  "@id": "Emotion0",
                  "@type": "Emotion",
                  "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#arousal": 6.44,
                  "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#dominance": 7.11,
                  "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#valence": 8.72,
                  "prov:wasGeneratedBy": "prefix:Analysis_1554364675.2834926"
                }
              ],
              "prov:wasGeneratedBy": "prefix:Analysis_1554364675.2834926"
            },
            {
              "@type": "EmotionSet",
              "onyx:hasEmotion": [
                {
                  "@type": "Emotion",
                  "onyx:algorithmConfidence": 7.449999999999999,
                  "onyx:hasEmotionCategory": "emoml:big6fear"
                }
              ],
              "prov:wasGeneratedBy": "prefix:Analysis_1554364675.2902758"
            }
          ]
        }
      ]
    }
    
Because we don’t usually care about the original emotion, the conversion can be presented in three ways: - full: the original and converted emotions are included at the same level - filtered: the original emotion is replaced by the converted emotion - nested: the original emotion is replaced, but the converted emotion points to it For example, here’s how the ``nested`` structure would look like: .. code:: ipython3 res = requests.get(f'{endpoint}/emotion-anew', params={"input": "Senpy is a wonderful service and I love it", "emotion-model": "emoml:big6", "conversion": "nested"}) pretty(res.text) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL2Vtb3Rpb24tYW5ldz9pbnB1dD1TZW5weStpcythK3dvbmRlcmZ1bCtzZXJ2aWNlK2FuZCtJK2xvdmUraXQmZW1vdGlvbi1tb2RlbD1lbW9tbCUzQWJpZzYmY29udmVyc2lvbj1uZXN0ZWQj",
      "@type": "Results",
      "entries": [
        {
          "@id": "prefix:",
          "@type": "Entry",
          "marl:hasOpinion": [],
          "nif:isString": "Senpy is a wonderful service and I love it",
          "onyx:hasEmotionSet": [
            {
              "@type": "EmotionSet",
              "onyx:hasEmotion": [
                {
                  "@type": "Emotion",
                  "onyx:algorithmConfidence": 7.449999999999999,
                  "onyx:hasEmotionCategory": "emoml:big6fear"
                }
              ],
              "prov:wasDerivedFrom": {
                "@id": "Emotions0",
                "@type": "EmotionSet",
                "onyx:hasEmotion": [
                  {
                    "@id": "Emotion0",
                    "@type": "Emotion",
                    "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#arousal": 6.44,
                    "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#dominance": 7.11,
                    "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#valence": 8.72,
                    "prov:wasGeneratedBy": "prefix:Analysis_1554364675.4125388"
                  }
                ],
                "prov:wasGeneratedBy": "prefix:Analysis_1554364675.4125388"
              },
              "prov:wasGeneratedBy": "prefix:Analysis_1554364675.4143574"
            }
          ]
        }
      ]
    }
    
Again, for completion, we could get only the label with the ``fields`` parameter: .. code:: ipython3 res = requests.get(f'{endpoint}/emotion-anew', params={"input": "Senpy is a wonderful service and I love it", "emotion-model": "emoml:big6", "fields": 'entries[].[["nif:isString","onyx:hasEmotionSet"[]."onyx:hasEmotion"[]."onyx:hasEmotionCategory"][]][]', "conversion": "filtered"}) pretty(res.text) .. raw:: html
[["Senpy is a wonderful service and I love it", "emoml:big6fear"]]
    
Built-in client --------------- The built-in senpy client allows you to query any Senpy endpoint. We will illustrate how to use it with the public demo endpoint, and then show you how to spin up your own endpoint using docker. Building pipelines ------------------ You can query several senpy services in the same request. This feature is called pipelining, and the result of combining several plugins in a request is called a pipeline. The simplest way to use pipelines is to add every plugin you want to use to the URL, separated by either a slash or a comma. For instance, to get sentiment (``sentiment140``) and emotion (``depechemood``) annotations at the same time: .. code:: ipython3 res = requests.get(f'{endpoint}/sentiment140/emotion-depechemood', params={"input": "Senpy is a wonderful service"}) pretty(res.text) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL3NlbnRpbWVudDE0MC9lbW90aW9uLWRlcGVjaGVtb29kP2lucHV0PVNlbnB5K2lzK2Erd29uZGVyZnVsK3NlcnZpY2Uj",
      "@type": "Results",
      "entries": [
        {
          "@id": "prefix:",
          "@type": "Entry",
          "marl:hasOpinion": [
            {
              "@type": "Sentiment",
              "marl:hasPolarity": "marl:Neutral",
              "prov:wasGeneratedBy": "prefix:Analysis_1554364675.8928602"
            }
          ],
          "nif:isString": "Senpy is a wonderful service",
          "onyx:hasEmotionSet": [
            {
              "@type": "EmotionSet",
              "onyx:hasEmotion": [
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:negative-fear",
                  "onyx:hasEmotionIntensity": 0.06258366271018097
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:amusement",
                  "onyx:hasEmotionIntensity": 0.15784834034155437
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:anger",
                  "onyx:hasEmotionIntensity": 0.08728815135373413
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:annoyance",
                  "onyx:hasEmotionIntensity": 0.12184635680460143
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:indifference",
                  "onyx:hasEmotionIntensity": 0.1374081151031531
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:joy",
                  "onyx:hasEmotionIntensity": 0.12267040802346799
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:awe",
                  "onyx:hasEmotionIntensity": 0.21085262130713067
                },
                {
                  "@type": "Emotion",
                  "onyx:hasEmotionCategory": "wna:sadness",
                  "onyx:hasEmotionIntensity": 0.09950234435617733
                }
              ],
              "prov:wasGeneratedBy": "prefix:Analysis_1554364675.8937423"
            }
          ]
        }
      ]
    }
    
In a senpy pipeline, the call is processed by each plugin in sequence. The output of a plugin is used as input for the next one. Pipelines take the same parameters as the plugins they are made of. For example, if we want to split the original sentence before analysing its sentiment, we can use a pipeline made out of the ``split`` and the ``sentiment140`` plugins. ``split`` takes an extra parameter (``delimiter``) to select the type of splitting (by sentence or by paragraph), and ``sentiment140`` takes a ``language`` parameter. This is how the request looks like: .. code:: ipython3 res = requests.get(f'{endpoint}/split/sentiment140', params={"input": "Senpy is awesome. And services are composable.", "delimiter": "sentence", "language": "en", "outformat": "json-ld"}) pretty(res.text) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL3NwbGl0L3NlbnRpbWVudDE0MD9pbnB1dD1TZW5weStpcythd2Vzb21lLitBbmQrc2VydmljZXMrYXJlK2NvbXBvc2FibGUuJmRlbGltaXRlcj1zZW50ZW5jZSZsYW5ndWFnZT1lbiZvdXRmb3JtYXQ9anNvbi1sZCM%3D",
      "@type": "Results",
      "entries": [
        {
          "@id": "prefix:",
          "@type": "Entry",
          "marl:hasOpinion": [
            {
              "@type": "Sentiment",
              "marl:hasPolarity": "marl:Positive",
              "prov:wasGeneratedBy": "prefix:Analysis_1554364676.2060485"
            }
          ],
          "nif:isString": "Senpy is awesome. And services are composable.",
          "onyx:hasEmotionSet": []
        },
        {
          "@id": "prefix:#char=0,17",
          "@type": "Entry",
          "marl:hasOpinion": [
            {
              "@type": "Sentiment",
              "marl:hasPolarity": "marl:Positive",
              "prov:wasGeneratedBy": "prefix:Analysis_1554364676.2060485"
            }
          ],
          "nif:isString": "Senpy is awesome.",
          "onyx:hasEmotionSet": []
        },
        {
          "@id": "prefix:#char=18,46",
          "@type": "Entry",
          "marl:hasOpinion": [
            {
              "@type": "Sentiment",
              "marl:hasPolarity": "marl:Neutral",
              "prov:wasGeneratedBy": "prefix:Analysis_1554364676.2060485"
            }
          ],
          "nif:isString": "And services are composable.",
          "onyx:hasEmotionSet": []
        }
      ]
    }
    
As you can see, ``split`` creates two new entries, which are also annotated by ``sentiment140``. Once again, we could use the ``fields`` parameter to get a list of strings and labels: .. code:: ipython3 res = requests.get(f'{endpoint}/split/sentiment140', params={"input": "Senpy is awesome. And services are composable.", "delimiter": "sentence", "fields": 'entries[].[["nif:isString","marl:hasOpinion"[]."marl:hasPolarity"][]][]', "language": "en", "outformat": "json-ld"}) pretty(res.text) .. raw:: html
[["Senpy is awesome. And services are composable.", "marl:Positive"], ["Senpy is awesome.", "marl:Positive"], ["And services are composable.", "marl:Neutral"]]
    
Evaluation ---------- Sentiment analysis plugins can also be evaluated on a series of pre-defined datasets, using the ``gsitk`` tool. For instance, to evaluate the ``sentiment-vader`` plugin on the ``vader`` and ``sts`` datasets, we would simply call: .. code:: ipython3 res = requests.get(f'{endpoint}/evaluate', params={"algo": "sentiment-vader", "dataset": "vader,sts", 'outformat': 'json-ld' }) pretty(res.text) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL2V2YWx1YXRlLz9hbGdvPXNlbnRpbWVudC12YWRlciZkYXRhc2V0PXZhZGVyJTJDc3RzJm91dGZvcm1hdD1qc29uLWxkIw%3D%3D",
      "@type": "AggregatedEvaluation",
      "senpy:evaluations": [
        {
          "@type": "Evaluation",
          "evaluates": "endpoint:plugins/sentiment-vader_0.1.1__vader",
          "evaluatesOn": "vader",
          "metrics": [
            {
              "@type": "Accuracy",
              "value": 0.6907142857142857
            },
            {
              "@type": "Precision_macro",
              "value": 0.34535714285714286
            },
            {
              "@type": "Recall_macro",
              "value": 0.5
            },
            {
              "@type": "F1_macro",
              "value": 0.40853400929446554
            },
            {
              "@type": "F1_weighted",
              "value": 0.5643605528396403
            },
            {
              "@type": "F1_micro",
              "value": 0.6907142857142857
            },
            {
              "@type": "F1_macro",
              "value": 0.40853400929446554
            }
          ]
        },
        {
          "@type": "Evaluation",
          "evaluates": "endpoint:plugins/sentiment-vader_0.1.1__sts",
          "evaluatesOn": "sts",
          "metrics": [
            {
              "@type": "Accuracy",
              "value": 0.3107177974434612
            },
            {
              "@type": "Precision_macro",
              "value": 0.1553588987217306
            },
            {
              "@type": "Recall_macro",
              "value": 0.5
            },
            {
              "@type": "F1_macro",
              "value": 0.23705926481620407
            },
            {
              "@type": "F1_weighted",
              "value": 0.14731706525451424
            },
            {
              "@type": "F1_micro",
              "value": 0.3107177974434612
            },
            {
              "@type": "F1_macro",
              "value": 0.23705926481620407
            }
          ]
        }
      ]
    }
    
The same results can be visualized as a table in the Web interface: |image0| .. |image0| image:: evaluation-results.png **note**: to evaluate a plugin on a dataset, senpy will need to predict the labels of the entries using the plugin. This process might take long for plugins that use an external service, such as ``sentiment140``. Advanced topics --------------- Verbose output ~~~~~~~~~~~~~~ By default, senpy does not include information that might be too verbose, such as the parameters that were used in the analysis. You can instruct senpy to provide a more verbose output with the ``verbose`` parameter: .. code:: ipython3 import requests res = requests.get(f'{endpoint}/sentiment140', params={ "input": "Senpy is the best framework for semantic sentiment analysis, and very easy to use", "verbose": True}).text pretty(res) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL3NlbnRpbWVudDE0MD9pbnB1dD1TZW5weStpcyt0aGUrYmVzdCtmcmFtZXdvcmsrZm9yK3NlbWFudGljK3NlbnRpbWVudCthbmFseXNpcyUyQythbmQrdmVyeStlYXN5K3RvK3VzZSZ2ZXJib3NlPVRydWUj",
      "@type": "Results",
      "activities": [
        {
          "@id": "prefix:Analysis_1554364688.7944896",
          "@type": "Analysis",
          "marl:maxPolarityValue": 1,
          "marl:minPolarityValue": 0,
          "prov:used": [
            {
              "@type": "Parameter",
              "name": "input",
              "value": "Senpy is the best framework for semantic sentiment analysis, and very easy to use"
            },
            {
              "@type": "Parameter",
              "name": "verbose",
              "value": true
            },
            {
              "@type": "Parameter",
              "name": "in-headers",
              "value": false
            },
            {
              "@type": "Parameter",
              "name": "algorithm",
              "value": "default"
            },
            {
              "@type": "Parameter",
              "name": "expanded-jsonld",
              "value": false
            },
            {
              "@type": "Parameter",
              "name": "with-parameters",
              "value": false
            },
            {
              "@type": "Parameter",
              "name": "outformat",
              "value": "json-ld"
            },
            {
              "@type": "Parameter",
              "name": "help",
              "value": false
            },
            {
              "@type": "Parameter",
              "name": "aliases",
              "value": false
            },
            {
              "@type": "Parameter",
              "name": "conversion",
              "value": "full"
            },
            {
              "@type": "Parameter",
              "name": "intype",
              "value": "direct"
            },
            {
              "@type": "Parameter",
              "name": "informat",
              "value": "text"
            },
            {
              "@type": "Parameter",
              "name": "prefix",
              "value": ""
            },
            {
              "@type": "Parameter",
              "name": "urischeme",
              "value": "RFC5147String"
            },
            {
              "@type": "Parameter",
              "name": "language",
              "value": "auto"
            }
          ],
          "prov:wasAssociatedWith": "endpoint:plugins/sentiment140_0.2"
        }
      ],
      "entries": [
        {
          "@id": "prefix:",
          "@type": "Entry",
          "marl:hasOpinion": [
            {
              "@type": "Sentiment",
              "marl:hasPolarity": "marl:Positive",
              "prov:wasGeneratedBy": "prefix:Analysis_1554364688.7944896"
            }
          ],
          "nif:isString": "Senpy is the best framework for semantic sentiment analysis, and very easy to use",
          "onyx:hasEmotionSet": []
        }
      ]
    }
    
Getting help ~~~~~~~~~~~~ .. code:: ipython3 import requests res = requests.get(f'{endpoint}/', params={ "help": True}).text pretty(res) .. raw:: html
{
      "@context": "http://senpy.gsi.upm.es/api/contexts/YXBpLz9oZWxwPVRydWUj",
      "@type": "Help",
      "valid_parameters": {
        "algorithm": {
          "aliases": [
            "algorithms",
            "a",
            "algo"
          ],
          "default": "default",
          "description": "Algorithms that will be used to process the request.It may be a list of comma-separated names.",
          "processor": "string_to_tuple",
          "required": true
        },
        "aliases": {
          "@id": "aliases",
          "aliases": [],
          "default": false,
          "description": "Replace JSON properties with their aliases",
          "options": [
            true,
            false
          ],
          "required": true
        },
        "conversion": {
          "@id": "conversion",
          "default": "full",
          "description": "How to show the elements that have (not) been converted.\n\n* full: converted and original elements will appear side-by-side\n* filtered: only converted elements will be shown\n* nested: converted elements will be shown, and they will include a link to the original element\n(using `prov:wasGeneratedBy`).\n",
          "options": [
            "filtered",
            "nested",
            "full"
          ],
          "required": true
        },
        "emotion-model": {
          "@id": "emotionModel",
          "aliases": [
            "emoModel",
            "emotionModel"
          ],
          "description": "Emotion model to use in the response.\nSenpy will try to convert the output to this model automatically.\n\nExamples: `wna:liking` and `emoml:big6`.\n        ",
          "required": false
        },
        "expanded-jsonld": {
          "@id": "expanded-jsonld",
          "aliases": [
            "expanded",
            "expanded_jsonld"
          ],
          "default": false,
          "description": "use JSON-LD expansion to get full URIs",
          "options": [
            true,
            false
          ],
          "required": true
        },
        "fields": {
          "@id": "fields",
          "description": "A jmespath selector, that can be used to extract a new dictionary, array or value\nfrom the results.\njmespath is a powerful query language for json and/or dictionaries.\nIt allows you to change the structure (and data) of your objects through queries.\n\ne.g., the following expression gets a list of `[emotion label, intensity]` for each entry:\n`entries[].\"onyx:hasEmotionSet\"[].\"onyx:hasEmotion\"[][\"onyx:hasEmotionCategory\",\"onyx:hasEmotionIntensity\"]`\n\nFor more information, see: https://jmespath.org\n\n",
          "required": false
        },
        "help": {
          "@id": "help",
          "aliases": [
            "h"
          ],
          "default": false,
          "description": "Show additional help to know more about the possible parameters",
          "options": [
            true,
            false
          ],
          "required": true
        },
        "in-headers": {
          "aliases": [
            "headers",
            "inheaders",
            "inHeaders",
            "in-headers",
            "in_headers"
          ],
          "default": false,
          "description": "Only include the JSON-LD context in the headers",
          "options": [
            true,
            false
          ],
          "required": true
        },
        "informat": {
          "@id": "informat",
          "aliases": [
            "f"
          ],
          "default": "text",
          "description": "input format",
          "options": [
            "text",
            "json-ld"
          ],
          "required": false
        },
        "input": {
          "@id": "input",
          "aliases": [
            "i"
          ],
          "help": "Input text",
          "required": true
        },
        "intype": {
          "@id": "intype",
          "aliases": [
            "t"
          ],
          "default": "direct",
          "description": "input type",
          "options": [
            "direct",
            "url",
            "file"
          ],
          "required": false
        },
        "language": {
          "aliases": [
            "language",
            "l"
          ],
          "default": "en",
          "description": "language of the input",
          "options": [
            "es",
            "en"
          ],
          "required": true
        },
        "outformat": {
          "@id": "outformat",
          "aliases": [
            "o"
          ],
          "default": "json-ld",
          "description": "The data can be semantically formatted (JSON-LD, turtle or n-triples),\ngiven as a list of comma-separated fields (see the fields option) or constructed from a Jinja2\ntemplate (see the template option).",
          "options": [
            "json-ld",
            "turtle",
            "ntriples"
          ],
          "required": true
        },
        "prefix": {
          "@id": "prefix",
          "aliases": [
            "p"
          ],
          "default": "",
          "description": "prefix to use for new entities",
          "required": true
        },
        "template": {
          "@id": "template",
          "description": "Jinja2 template for the result. The input data for the template will\nbe the results as a dictionary.\nFor example:\n\nConsider the results before templating:\n\n```\n[{\n    \"@type\": \"entry\",\n    \"onyx:hasEmotionSet\": [],\n    \"nif:isString\": \"testing the template\",\n    \"marl:hasOpinion\": [\n        {\n            \"@type\": \"sentiment\",\n            \"marl:hasPolarity\": \"marl:Positive\"\n        }\n    ]\n}]\n```\n\n\nAnd the template:\n\n```\n{% for entry in entries %}\n{{ entry[\"nif:isString\"] | upper }},{{entry.sentiments[0][\"marl:hasPolarity\"].split(\":\")[1]}}\n{% endfor %}\n```\n\nThe final result would be:\n\n```\nTESTING THE TEMPLATE,Positive\n```\n",
          "required": false
        },
        "urischeme": {
          "@id": "urischeme",
          "aliases": [
            "u"
          ],
          "default": "RFC5147String",
          "description": "scheme for NIF URIs",
          "options": [
            "RFC5147String"
          ],
          "required": false
        },
        "verbose": {
          "@id": "verbose",
          "aliases": [
            "v"
          ],
          "default": false,
          "description": "Show all properties in the result",
          "options": [
            true,
            false
          ],
          "required": true
        },
        "with-parameters": {
          "aliases": [
            "withparameters",
            "with_parameters"
          ],
          "default": false,
          "description": "include initial parameters in the response",
          "options": [
            true,
            false
          ],
          "required": true
        }
      }
    }
    
Ignoring the context ~~~~~~~~~~~~~~~~~~~~ .. code:: ipython3 import requests res = requests.get(f'{endpoint}/', params={ "input": "This will tell senpy to only include the context in the headers", "inheaders": True}) pretty(res.text) .. raw:: html
{
      "@type": "Results",
      "entries": [
        {
          "@id": "prefix:",
          "@type": "Entry",
          "marl:hasOpinion": [],
          "nif:isString": "This will tell senpy to only include the context in the headers",
          "onyx:hasEmotionSet": [
            {
              "@id": "Emotions0",
              "@type": "EmotionSet",
              "onyx:hasEmotion": [
                {
                  "@id": "Emotion0",
                  "@type": "Emotion",
                  "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#arousal": 4.22,
                  "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#dominance": 5.17,
                  "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/anew/ns#valence": 5.2,
                  "prov:wasGeneratedBy": "prefix:Analysis_1554364689.0180304"
                }
              ],
              "prov:wasGeneratedBy": "prefix:Analysis_1554364689.0180304"
            }
          ]
        }
      ]
    }
    
To retrieve the context URI, use the ``LINK`` header: .. code:: ipython3 print(res.headers['Link']) .. parsed-literal:: ;rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"