From ac8f0b9b4b93978bd2634a78f6e15aa3e6d13113 Mon Sep 17 00:00:00 2001 From: Kc Jordan Date: Mon, 11 Dec 2023 15:49:58 -0500 Subject: [PATCH] Added new Transformer 'OpennessRuleScore' that performs a rule-based metric on questions to determine their 'openness' --- .../OpennessRuleScoreScript.ipynb | 472 ++++++++++++++++++ convokit/openness_rule/opennessRuleScore.py | 159 ++++++ convokit/openness_rule/pull_request.md | 20 + 3 files changed, 651 insertions(+) create mode 100644 convokit/openness_rule/OpennessRuleScoreScript.ipynb create mode 100644 convokit/openness_rule/opennessRuleScore.py create mode 100644 convokit/openness_rule/pull_request.md diff --git a/convokit/openness_rule/OpennessRuleScoreScript.ipynb b/convokit/openness_rule/OpennessRuleScoreScript.ipynb new file mode 100644 index 00000000..31906ddd --- /dev/null +++ b/convokit/openness_rule/OpennessRuleScoreScript.ipynb @@ -0,0 +1,472 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 60, + "id": "57a1b3ff-d9cb-4d1d-9b64-5e1693e4ba63", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 393 + }, + "executionInfo": { + "elapsed": 2258, + "status": "error", + "timestamp": 1702236835431, + "user": { + "displayName": "Kassandra Jordan", + "userId": "09149191759086076767" + }, + "user_tz": 300 + }, + "id": "57a1b3ff-d9cb-4d1d-9b64-5e1693e4ba63", + "outputId": "ac0d5397-a5d8-4e5a-f82c-1ca46e40b405" + }, + "outputs": [], + "source": [ + "from collections import defaultdict\n", + "from tqdm import tqdm\n", + "from itertools import permutations\n", + "from nltk.tokenize import word_tokenize, sent_tokenize\n", + "from convokit import Corpus, download\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import random\n", + "import language_tool_python\n", + "from convokit import text_processing\n", + "from convokit.transformer import Transformer\n", + "# import nltk" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "32fb77e3-236c-4877-ae47-19ced3321af5", + "metadata": { + "executionInfo": { + "elapsed": 8, + "status": "aborted", + "timestamp": 1702236835432, + "user": { + "displayName": "Kassandra Jordan", + "userId": "09149191759086076767" + }, + "user_tz": 300 + }, + "id": "32fb77e3-236c-4877-ae47-19ced3321af5" + }, + "outputs": [], + "source": [ + "PATH = '/Users/kcjordan/Code/cs4350/CANDOR-corpus-cliffhanger'\n", + "data_dir = f'{PATH}'\n", + "\n", + "corpus = Corpus(filename=data_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "a085c56b-aed9-4ad5-875d-f3c5fc9dbd54", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Utterance(id: '263955', conversation_id: c4d50c68-5b6d-4180-a693-25cab706ada4, reply-to: 263954, speaker: Speaker(id: '5f4fd45ac62ac40f6bdf8ee1', vectors: [], meta: ConvoKitMeta({'sex': 'male', 'politics': 2.0, 'race': 'black_or_african_american', 'edu': 'masters_degree', 'employ': 'unemployed', 'age': 28.0})), timestamp: 203.14, text: 'Yeah. Hello. Yeah how you doing?', vectors: [], meta: ConvoKitMeta({'turn_id': 1, 'start': 203.14, 'stop': 211.55, 'interval': -0.7200000000000273, 'delta': 8.410000000000025, 'questions': 1, 'end_question': True, 'overlap': True, 'n_words': 6}))\n" + ] + } + ], + "source": [ + "#Example utterance\n", + "convo = corpus.random_conversation()\n", + "for utt in convo.iter_utterances():\n", + " if utt.meta['questions'] > 0:\n", + " before = utt\n", + " break\n", + "print(before)" + ] + }, + { + "cell_type": "markdown", + "id": "06e90f1a-c39c-4c26-a9ae-51c93a1a1c42", + "metadata": { + "id": "06e90f1a-c39c-4c26-a9ae-51c93a1a1c42" + }, + "source": [ + "# Transformer" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "99d640b1-f6c5-4958-9bcf-0308124dae82", + "metadata": { + "executionInfo": { + "elapsed": 7, + "status": "aborted", + "timestamp": 1702236835432, + "user": { + "displayName": "Kassandra Jordan", + "userId": "09149191759086076767" + }, + "user_tz": 300 + }, + "id": "99d640b1-f6c5-4958-9bcf-0308124dae82" + }, + "outputs": [], + "source": [ + "class OpennessRuleScore(Transformer):\n", + " \"\"\"\n", + " A simple transformer to count all the usage of \"open\" or \"closed\" indicator words in the last question posed.\n", + "\n", + " :param obj_type: type of Corpus object to calculate: 'conversation', 'speaker', or 'utterance', default to be 'utterance'\n", + " :param input_field: Input fields from every utterance object. Will default to reading 'utt.text'. If a string is provided, than consider metadata with field name input_field.\n", + " :param output_field: field for writing the computed output in metadata. Will default to write to utterance metadata with name 'openness'.\n", + " :param input_filter: a boolean function of signature `input_filter(utterance, aux_input)`. attributes will only be computed for utterances where `input_filter` returns `True`. By default, will always return `True`, meaning that attributes will be computed for all utterances.\n", + " :param verbosity: frequency at which to print status messages when computing attributes.\n", + " \"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " obj_type='utterance',\n", + " output_field='openness_score',\n", + " input_field=None,\n", + " input_filter=None,\n", + " verbosity=10000,\n", + " ):\n", + " if input_filter:\n", + " if len(signature(input_filter).parameters) == 1:\n", + " self.input_filter = lambda utt: input_filter(utt)\n", + " else:\n", + " self.input_filter = input_filter\n", + " else:\n", + " self.input_filter = lambda utt: True\n", + " self.obj_type = obj_type\n", + " self.input_field = input_field\n", + " self.output_field = output_field\n", + " self.verbosity = verbosity\n", + " self.open = [\n", + " \"what\",\n", + " \"do\",\n", + " \"does\",\n", + " \"wonder\",\n", + " \"think\",\n", + " \"you\",\n", + " \"why\",\n", + " \"how\",\n", + " \"should\",\n", + " \"your\"]\n", + " self.closed = [\n", + " \"I\",\n", + " \"right\",\n", + " \"know\",\n", + " \"can\",\n", + " \"or\",\n", + " \"much\",\n", + " \"many\",\n", + " \"long\",\n", + " \"have\",\n", + " \"where\",\n", + " \"when\",\n", + " \"who\",\n", + " \"which\",\n", + " \"yes\",\n", + " \"yeah\",\n", + " \"no\"]\n", + "\n", + " def _print_output(self, i):\n", + " return (self.verbosity > 0) and (i > 0) and (i % self.verbosity == 0)\n", + "\n", + " def find_last_question(self, text):\n", + " #we return the last question\n", + " sent = sent_tokenize(text)\n", + " for sent_tok in reversed(sent):\n", + " if sent_tok[-1]==\"?\":\n", + " return sent_tok\n", + " #no question in this utterance\n", + " return \"\"\n", + "\n", + " def rule_score_for_utterance(self, text):\n", + " score = 0\n", + " for el in text:\n", + " if el in self.closed:\n", + " score -=1\n", + " if el in self.open:\n", + " score +=1\n", + " return score\n", + "\n", + " def transform(self, corpus: Corpus) -> Corpus:\n", + " \"\"\"\n", + " Score the amount of \"openness_score\" and annotate in the corresponding object metadata fields.\n", + "\n", + " :param corpus: Corpus\n", + " :return: the corpus\n", + " \"\"\"\n", + " if self.obj_type == 'utterance':\n", + " total = len(list(corpus.iter_utterances()))\n", + "\n", + " for idx, utterance in enumerate(corpus.iter_utterances()):\n", + " if self._print_output(idx):\n", + " print(f\"%03d/%03d {self.obj_type} processed\" % (idx, total))\n", + " if not self.input_filter(utterance):\n", + " continue\n", + " if self.input_field is None:\n", + " text_entry = utterance.text\n", + " elif isinstance(self.input_field, str):\n", + " text_entry = utterance.meta(self.input_field)\n", + " if text_entry is None:\n", + " continue\n", + "\n", + " sent = self.find_last_question(text_entry.lower())\n", + " tok = word_tokenize(sent)\n", + " catch = self.rule_score_for_utterance(tok)\n", + "\n", + " utterance.add_meta(self.output_field, catch)\n", + "\n", + " elif self.obj_type == 'conversation':\n", + " total = len(list(corpus.iter_conversations()))\n", + "\n", + " for idx, convo in enumerate(corpus.iter_conversations()):\n", + " if self._print_output(idx):\n", + " print(f\"%03d/%03d {self.obj_type} processed\" % (idx, total))\n", + "\n", + " if not self.input_filter(convo):\n", + " continue\n", + "\n", + " if self.input_field is None:\n", + " utt_lst = convo.get_utterance_ids()\n", + " text_entry = ' '.join([corpus.get_utterance(x).text for x in utt_lst])\n", + " elif isinstance(self.input_field, str):\n", + " text_entry = convo.meta(self.input_field)\n", + " if text_entry is None:\n", + " continue\n", + "\n", + " sent = self.find_last_question(text_entry.lower())\n", + " tok = word_tokenize(sent)\n", + " catch = self.rule_score_for_utterance(tok)\n", + "\n", + " convo.add_meta(self.output_field, catch)\n", + "\n", + " elif self.obj_type == 'speaker':\n", + " total = len(list(corpus.iter_speakers()))\n", + "\n", + " for idx, sp in enumerate(corpus.iter_speakers()):\n", + " if self._print_output(idx):\n", + " print(f\"%03d/%03d {self.obj_type} processed\" % (idx, total))\n", + "\n", + " if not self.input_filter(sp):\n", + " continue\n", + "\n", + " if self.input_field is None:\n", + " utt_lst = sp.get_utterance_ids()\n", + " text_entry = ' '.join([corpus.get_utterance(x).text for x in utt_lst])\n", + " elif isinstance(self.input_field, str):\n", + " text_entry = sp.meta(self.input_field)\n", + " if text_entry is None:\n", + " continue\n", + "\n", + " sent = self.find_last_question(text_entry.lower())\n", + " tok = word_tokenize(sent)\n", + " catch = self.rule_score_for_utterance(tok)\n", + "\n", + " sp.add_meta(self.output_field, catch)\n", + "\n", + " else:\n", + " raise KeyError('obj_type must be utterance, conversation, or speaker')\n", + "\n", + "\n", + " if self.verbosity > 0:\n", + " print(f\"%03d/%03d {self.obj_type} processed\" % (total, total))\n", + " return corpus" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "d3deaa5a-6817-4964-be8f-f2040e03d1ed", + "metadata": { + "executionInfo": { + "elapsed": 7, + "status": "aborted", + "timestamp": 1702236835432, + "user": { + "displayName": "Kassandra Jordan", + "userId": "09149191759086076767" + }, + "user_tz": 300 + }, + "id": "d3deaa5a-6817-4964-be8f-f2040e03d1ed" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10000/527869 utterance processed\n", + "20000/527869 utterance processed\n", + "30000/527869 utterance processed\n", + "40000/527869 utterance processed\n", + "50000/527869 utterance processed\n", + "60000/527869 utterance processed\n", + "70000/527869 utterance processed\n", + "80000/527869 utterance processed\n", + "90000/527869 utterance processed\n", + "100000/527869 utterance processed\n", + "110000/527869 utterance processed\n", + "120000/527869 utterance processed\n", + "130000/527869 utterance processed\n", + "140000/527869 utterance processed\n", + "150000/527869 utterance processed\n", + "160000/527869 utterance processed\n", + "170000/527869 utterance processed\n", + "180000/527869 utterance processed\n", + "190000/527869 utterance processed\n", + "200000/527869 utterance processed\n", + "210000/527869 utterance processed\n", + "220000/527869 utterance processed\n", + "230000/527869 utterance processed\n", + "240000/527869 utterance processed\n", + "250000/527869 utterance processed\n", + "260000/527869 utterance processed\n", + "270000/527869 utterance processed\n", + "280000/527869 utterance processed\n", + "290000/527869 utterance processed\n", + "300000/527869 utterance processed\n", + "310000/527869 utterance processed\n", + "320000/527869 utterance processed\n", + "330000/527869 utterance processed\n", + "340000/527869 utterance processed\n", + "350000/527869 utterance processed\n", + "360000/527869 utterance processed\n", + "370000/527869 utterance processed\n", + "380000/527869 utterance processed\n", + "390000/527869 utterance processed\n", + "400000/527869 utterance processed\n", + "410000/527869 utterance processed\n", + "420000/527869 utterance processed\n", + "430000/527869 utterance processed\n", + "440000/527869 utterance processed\n", + "450000/527869 utterance processed\n", + "460000/527869 utterance processed\n", + "470000/527869 utterance processed\n", + "480000/527869 utterance processed\n", + "490000/527869 utterance processed\n", + "500000/527869 utterance processed\n", + "510000/527869 utterance processed\n", + "520000/527869 utterance processed\n", + "527869/527869 utterance processed\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "opencounter = OpennessRuleScore(obj_type='utterance')\n", + "opencounter.transform(corpus)" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "0cffd1b8-0b5c-45b9-aa71-7961791d783a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Yeah. Hello. Yeah how you doing?\n", + "Utterance(id: '263955', conversation_id: c4d50c68-5b6d-4180-a693-25cab706ada4, reply-to: 263954, speaker: Speaker(id: '5f4fd45ac62ac40f6bdf8ee1', vectors: [], meta: ConvoKitMeta({'sex': 'male', 'politics': 2.0, 'race': 'black_or_african_american', 'edu': 'masters_degree', 'employ': 'unemployed', 'age': 28.0})), timestamp: 203.14, text: 'Yeah. Hello. Yeah how you doing?', vectors: [], meta: ConvoKitMeta({'turn_id': 1, 'start': 203.14, 'stop': 211.55, 'interval': -0.7200000000000273, 'delta': 8.410000000000025, 'questions': 1, 'end_question': True, 'overlap': True, 'n_words': 6, 'openness_score': 1}))\n" + ] + } + ], + "source": [ + "after = corpus.get_utterance(before.id)\n", + "print(after.text)\n", + "print(after)" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "6b0091e4-36e5-4e7d-a079-19ece0ce5b19", + "metadata": { + "executionInfo": { + "elapsed": 7, + "status": "aborted", + "timestamp": 1702236835432, + "user": { + "displayName": "Kassandra Jordan", + "userId": "09149191759086076767" + }, + "user_tz": 300 + }, + "id": "6b0091e4-36e5-4e7d-a079-19ece0ce5b19" + }, + "outputs": [ + { + "data": { + "image/png": "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u92aPn26CgoK9O6776qiokLTpk2Tz+fT6NGjJUnjxo1TZmampk6dqj/84Q/avn27Fi5cqPz8fDmdTknSE088oT//+c+aO3euDh06pLVr12rz5s2aM2eO3UdBQYF++ctfasOGDfr44481Y8YMNTQ0aNq0aVdxagAAQHsW1T05n3/+uSZPnqyvvvpKPXv21J133qn3339fPXv2lCS99NJLio+P14QJE9TY2Ci/36+1a9fa2yckJGjr1q2aMWOGfD6funTpory8PC1ZssSuycjIUHFxsebMmaNVq1apd+/eevXVV+X3++2aiRMn6vjx4yosLFQwGNSwYcNUUlJyzs3IAADgxhXVe3JMw3tyALPxnhzATNfsPTkAAADtASEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAw0hWFnGXLlikuLk6zZ8+21506dUr5+fnq3r27unbtqgkTJqimpiZiu6qqKuXm5qpz585KSUnRM888ozNnzkTU7Ny5U8OHD5fT6VT//v1VVFR0zvHXrFmjfv36KTExUV6vV3v37r2S0wEAAAa57JDzwQcf6Be/+IWGDh0asX7OnDnasmWL3nrrLe3atUvV1dV68MEH7fHm5mbl5uaqqalJe/bs0YYNG1RUVKTCwkK75siRI8rNzdWYMWNUWVmp2bNn69FHH9X27dvtmk2bNqmgoECLFi3Svn37lJWVJb/fr9ra2ss9JQAAYJA4y7KsaDc6efKkhg8frrVr1+r555/XsGHDtHLlStXX16tnz57auHGjHnroIUnSoUOHNGjQIAUCAY0ePVrbtm3Tfffdp+rqaqWmpkqS1q9fr3nz5un48eNyOByaN2+eiouLdeDAAfuY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9+SEw2E1NjYqOztbHTt2VFlZmT12+PBhVVVVyefzSZJ8Pp/2798f8RRUaWmpXC6XMjMz7Zqz99FS07IPh8Oh7OzsiJpwOKyysjK7BgAAIKorOQsWLNC9996rPn366MSJE9q4caN27typ7du3y+12a/r06SooKFBycrJcLpdmzZoln8+n0aNHS5LGjRunzMxMTZ06VcuXL1cwGNTChQuVn59vX2F54okntHr1as2dO1ePPPKIduzYoc2bN6u4+G9PSRQUFCgvL08jRozQqFGjtHLlSjU0NGjatGlXcWoAAEB7FlXIqa2t1Q9/+EN98cUXcrvdGjp0qLZv365/+qd/kiS99NJLio+P14QJE9TY2Ci/36+1a9fa2yckJGjr1q2aMWOGfD6funTpory8PC1ZssSuycjIUHFxsebMmaNVq1apd+/eevXVV+X3++2aiRMn6vjx4yosLFQwGNSwYcNUUlJyzs3IAADgxnXF78lpz3hPDmA23pMDmOmavycHAACgLSPkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARooq5CxdulQjR45Ut27dlJKSovHjx+vw4cMRNadOnVJ+fr66d++url27asKECaqpqYmoqaqqUm5urjp37qyUlBQ988wzOnPmTETNzp07NXz4cDmdTvXv319FRUXn9LNmzRr169dPiYmJ8nq92rt3bzSnAwAADBZVyNm1a5fy8/P1/vvvq7S0VKdPn9a4cePU0NBg18yZM0dbtmzRW2+9pV27dqm6uloPPvigPd7c3Kzc3Fw1NTVpz5492rBhg4qKilRYWGjXHDlyRLm5uRozZowqKys1e/ZsPfroo9q+fbtds2nTJhUUFGjRokXat2+fsrKy5Pf7VVtbeyXzAQAADBFnWZZ1uRsfP35cKSkp2rVrl+6++27V19erZ8+e2rhxox566CFJ0qFDhzRo0CAFAgGNHj1a27Zt03333afq6mqlpqZKktavX6958+bp+PHjcjgcmjdvnoqLi3XgwAH7WJMmTVJdXZ1KSkokSV6vVyNHjtTq1aslSeFwWOnp6Zo1a5bmz59/Sf2HQiG53W7V19fL5XJd7jQAaKP6zS+OdQtRO7osN9YtAG3epX5/X9E9OfX19ZKk5ORkSVJFRYVOnz6tnJwcu2bgwIHq06ePAoGAJCkQCGjIkCF2wJEkv9+vUCikgwcP2jVn76OlpmUfTU1NqqioiKiJj49XTk6OXdOaxsZGhUKhiAUAAJjpskNOOBzW7Nmzdccdd2jw4MGSpGAwKIfDoaSkpIja1NRUBYNBu+bsgNMy3jJ2oZpQKKRvv/1WX375pZqbm1utadlHa5YuXSq3220v6enp0Z84AABoFy475OTn5+vAgQN68803r2Y/19SCBQtUX19vL8eOHYt1SwAA4BrpcDkbzZw5U1u3btXu3bvVu3dve73H41FTU5Pq6uoirubU1NTI4/HYNX//FFTL01dn1/z9E1k1NTVyuVzq1KmTEhISlJCQ0GpNyz5a43Q65XQ6oz9hAADQ7kR1JceyLM2cOVNvv/22duzYoYyMjIjx7OxsdezYUWVlZfa6w4cPq6qqSj6fT5Lk8/m0f//+iKegSktL5XK5lJmZadecvY+WmpZ9OBwOZWdnR9SEw2GVlZXZNQAA4MYW1ZWc/Px8bdy4Ub/61a/UrVs3+/4Xt9utTp06ye12a/r06SooKFBycrJcLpdmzZoln8+n0aNHS5LGjRunzMxMTZ06VcuXL1cwGNTChQuVn59vX2V54okntHr1as2dO1ePPPKIduzYoc2bN6u4+G9PShQUFCgvL08jRozQqFGjtHLlSjU0NGjatGlXa24AAEA7FlXIWbdunSTpH//xHyPWv/baa3r44YclSS+99JLi4+M1YcIENTY2yu/3a+3atXZtQkKCtm7dqhkzZsjn86lLly7Ky8vTkiVL7JqMjAwVFxdrzpw5WrVqlXr37q1XX31Vfr/frpk4caKOHz+uwsJCBYNBDRs2TCUlJefcjAwAAG5MV/SenPaO9+QAZuM9OYCZrst7cgAAANoqQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAI3WIdQMA2od+84tj3QIARIUrOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwUtQhZ/fu3br//vuVlpamuLg4vfPOOxHjlmWpsLBQvXr1UqdOnZSTk6NPPvkkoubrr7/WlClT5HK5lJSUpOnTp+vkyZMRNX/84x911113KTExUenp6Vq+fPk5vbz11lsaOHCgEhMTNWTIEP3mN7+J9nQAAIChog45DQ0NysrK0po1a1odX758uV5++WWtX79e5eXl6tKli/x+v06dOmXXTJkyRQcPHlRpaam2bt2q3bt36/HHH7fHQ6GQxo0bp759+6qiokIrVqzQ4sWL9corr9g1e/bs0eTJkzV9+nT9/ve/1/jx4zV+/HgdOHAg2lMCAAAGirMsy7rsjePi9Pbbb2v8+PGS/noVJy0tTU899ZSefvppSVJ9fb1SU1NVVFSkSZMm6eOPP1ZmZqY++OADjRgxQpJUUlKif/7nf9bnn3+utLQ0rVu3Tv/xH/+hYDAoh8MhSZo/f77eeecdHTp0SJI0ceJENTQ0aOvWrXY/o0eP1rBhw7R+/fpL6j8UCsntdqu+vl4ul+typwG4IfB/Ib8+ji7LjXULQJt3qd/fV/WenCNHjigYDConJ8de53a75fV6FQgEJEmBQEBJSUl2wJGknJwcxcfHq7y83K65++677YAjSX6/X4cPH9Y333xj15x9nJaaluO0prGxUaFQKGIBAABmuqohJxgMSpJSU1Mj1qemptpjwWBQKSkpEeMdOnRQcnJyRE1r+zj7GOeraRlvzdKlS+V2u+0lPT092lMEAADtxA31dNWCBQtUX19vL8eOHYt1SwAA4Bq5qiHH4/FIkmpqaiLW19TU2GMej0e1tbUR42fOnNHXX38dUdPaPs4+xvlqWsZb43Q65XK5IhYAAGCmqxpyMjIy5PF4VFZWZq8LhUIqLy+Xz+eTJPl8PtXV1amiosKu2bFjh8LhsLxer12ze/dunT592q4pLS3VgAEDdNNNN9k1Zx+npablOAAA4MYWdcg5efKkKisrVVlZKemvNxtXVlaqqqpKcXFxmj17tp5//nn9+te/1v79+/XDH/5QaWlp9hNYgwYN0j333KPHHntMe/fu1e9+9zvNnDlTkyZNUlpamiTpBz/4gRwOh6ZPn66DBw9q06ZNWrVqlQoKCuw+fvzjH6ukpEQ/+9nPdOjQIS1evFgffvihZs6ceeWzAgAA2r0O0W7w4YcfasyYMfbPLcEjLy9PRUVFmjt3rhoaGvT444+rrq5Od955p0pKSpSYmGhv8/rrr2vmzJkaO3as4uPjNWHCBL388sv2uNvt1m9/+1vl5+crOztbPXr0UGFhYcS7dL773e9q48aNWrhwoX7yk5/olltu0TvvvKPBgwdf1kQAAACzXNF7cto73pMDXDrek3N98J4c4OJi8p4cAACAtoKQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASFG/JwcAcO20x0f1eewdbRVXcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIHWLdwJVas2aNVqxYoWAwqKysLP385z/XqFGjYt0WANww+s0vjnULUTu6LDfWLeA6aNdXcjZt2qSCggItWrRI+/btU1ZWlvx+v2pra2PdGgAAiLE4y7KsWDdxubxer0aOHKnVq1dLksLhsNLT0zVr1izNnz//otuHQiG53W7V19fL5XJd63YBSe3zv3oB03Alp3271O/vdvvXVU1NTaqoqNCCBQvsdfHx8crJyVEgEGh1m8bGRjU2Nto/19fXS/rrZAHXS7jxL7FuAbjh8Xu/fWv553ex6zTtNuR8+eWXam5uVmpqasT61NRUHTp0qNVtli5dqueee+6c9enp6dekRwBA2+ReGesOcDWcOHFCbrf7vOPtNuRcjgULFqigoMD+ORwO6+uvv1b37t0VFxcXw87+JhQKKT09XceOHeOv0FrB/Fwcc3RxzNHFMUcXxvxc3LWcI8uydOLECaWlpV2wrt2GnB49eighIUE1NTUR62tqauTxeFrdxul0yul0RqxLSkq6Vi1eEZfLxb84F8D8XBxzdHHM0cUxRxfG/FzctZqjC13BadFun65yOBzKzs5WWVmZvS4cDqusrEw+ny+GnQEAgLag3V7JkaSCggLl5eVpxIgRGjVqlFauXKmGhgZNmzYt1q0BAIAYa9chZ+LEiTp+/LgKCwsVDAY1bNgwlZSUnHMzcnvidDq1aNGic/5aDX/F/Fwcc3RxzNHFMUcXxvxcXFuYo3b9nhwAAIDzabf35AAAAFwIIQcAABiJkAMAAIxEyAEAAEYi5LQhL7zwgr773e+qc+fO531JYVxc3DnLm2++eX0bjZFLmZ+qqirl5uaqc+fOSklJ0TPPPKMzZ85c30bbkH79+p3zeVm2bFms24qpNWvWqF+/fkpMTJTX69XevXtj3VKbsXjx4nM+LwMHDox1WzG1e/du3X///UpLS1NcXJzeeeediHHLslRYWKhevXqpU6dOysnJ0SeffBKbZmPkYnP08MMPn/O5uueee65Lb4ScNqSpqUnf//73NWPGjAvWvfbaa/riiy/sZfz48denwRi72Pw0NzcrNzdXTU1N2rNnjzZs2KCioiIVFhZe507bliVLlkR8XmbNmhXrlmJm06ZNKigo0KJFi7Rv3z5lZWXJ7/ertrY21q21GbfddlvE5+W9996LdUsx1dDQoKysLK1Zs6bV8eXLl+vll1/W+vXrVV5eri5dusjv9+vUqVPXudPYudgcSdI999wT8bl64403rk9zFtqc1157zXK73a2OSbLefvvt69pPW3O++fnNb35jxcfHW8Fg0F63bt06y+VyWY2Njdexw7ajb9++1ksvvRTrNtqMUaNGWfn5+fbPzc3NVlpamrV06dIYdtV2LFq0yMrKyop1G23W3//+DYfDlsfjsVasWGGvq6urs5xOp/XGG2/EoMPYa+07Ki8vz3rggQdi0g9Xctqh/Px89ejRQ6NGjdL//M//XPR/NX+jCAQCGjJkSMTLIP1+v0KhkA4ePBjDzmJr2bJl6t69u26//XatWLHihv3ru6amJlVUVCgnJ8deFx8fr5ycHAUCgRh21rZ88sknSktL080336wpU6aoqqoq1i21WUeOHFEwGIz4TLndbnm9Xj5Tf2fnzp1KSUnRgAEDNGPGDH311VfX5bjt+o3HN6IlS5boe9/7njp37qzf/va3+vd//3edPHlSTz75ZKxbi7lgMHjO265bfg4Gg7FoKeaefPJJDR8+XMnJydqzZ48WLFigL774Qv/93/8d69auuy+//FLNzc2tfkYOHToUo67aFq/Xq6KiIg0YMEBffPGFnnvuOd111106cOCAunXrFuv22pyW3yutfaZu1N85rbnnnnv04IMPKiMjQ5999pl+8pOf6N5771UgEFBCQsI1PTYh5xqbP3++fvrTn16w5uOPP77km/ueffZZ+8+33367GhoatGLFinYbcq72/NwIopmzgoICe93QoUPlcDj0ox/9SEuXLuV19DjHvffea/956NCh8nq96tu3rzZv3qzp06fHsDO0Z5MmTbL/PGTIEA0dOlT/8A//oJ07d2rs2LHX9NiEnGvsqaee0sMPP3zBmptvvvmy9+/1evWf//mfamxsbJdfWldzfjwezzlPytTU1NhjpriSOfN6vTpz5oyOHj2qAQMGXIPu2q4ePXooISHB/ky0qKmpMerzcTUlJSXp1ltv1aeffhrrVtqkls9NTU2NevXqZa+vqanRsGHDYtRV23fzzTerR48e+vTTTwk57V3Pnj3Vs2fPa7b/yspK3XTTTe0y4EhXd358Pp9eeOEF1dbWKiUlRZJUWloql8ulzMzMq3KMtuBK5qyyslLx8fH2/NxIHA6HsrOzVVZWZj+RGA6HVVZWppkzZ8a2uTbq5MmT+uyzzzR16tRYt9ImZWRkyOPxqKyszA41oVBI5eXlF31K9kb2+eef66uvvooIhtcKIacNqaqq0tdff62qqio1NzersrJSktS/f3917dpVW7ZsUU1NjUaPHq3ExESVlpbqxRdf1NNPPx3bxq+Ti83PuHHjlJmZqalTp2r58uUKBoNauHCh8vPz220IvBKBQEDl5eUaM2aMunXrpkAgoDlz5ujf/u3fdNNNN8W6vZgoKChQXl6eRowYoVGjRmnlypVqaGjQtGnTYt1am/D000/r/vvvV9++fVVdXa1FixYpISFBkydPjnVrMXPy5MmIK1lHjhxRZWWlkpOT1adPH82ePVvPP/+8brnlFmVkZOjZZ59VWlraDfNqD+nCc5ScnKznnntOEyZMkMfj0Weffaa5c+eqf//+8vv91765mDzThVbl5eVZks5Z3n33XcuyLGvbtm3WsGHDrK5du1pdunSxsrKyrPXr11vNzc2xbfw6udj8WJZlHT161Lr33nutTp06WT169LCeeuop6/Tp07FrOoYqKiosr9drud1uKzEx0Ro0aJD14osvWqdOnYp1azH185//3OrTp4/lcDisUaNGWe+//36sW2ozJk6caPXq1ctyOBzWd77zHWvixInWp59+Guu2Yurdd99t9fdOXl6eZVl/fYz82WeftVJTUy2n02mNHTvWOnz4cGybvs4uNEd/+ctfrHHjxlk9e/a0OnbsaPXt29d67LHHIl71cS3FWRbPHwMAAPPwnhwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjPT/YKUq4dUi+9cAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "open = []\n", + "text = []\n", + "max = (0, \"\")\n", + "min = (0, \"\")\n", + "for utt in corpus.iter_utterances():\n", + " if utt.meta['questions'] > 0:\n", + " score = utt.meta['openness_score']\n", + " open.append(score)\n", + " if score > max[0]:\n", + " max = (score, utt.text)\n", + " if score < min[0]:\n", + " min = (score, utt.text)\n", + " if score == 0:\n", + " text.append(utt.text)\n", + "\n", + "plt.hist(open)\n", + "plt.show()" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/convokit/openness_rule/opennessRuleScore.py b/convokit/openness_rule/opennessRuleScore.py new file mode 100644 index 00000000..d43f0def --- /dev/null +++ b/convokit/openness_rule/opennessRuleScore.py @@ -0,0 +1,159 @@ +from convokit.transformer import Transformer +from inspect import signature +from nltk.tokenize import word_tokenize, sent_tokenize +from convokit.model import Corpus + + +class OpennessRuleScore(Transformer): + """ + A simple transformer to count all the usage of "open" or "closed" indicator words in the last question posed. + + :param obj_type: type of Corpus object to calculate: 'conversation', 'speaker', or 'utterance', default to be 'utterance' + :param input_field: Input fields from every utterance object. Will default to reading 'utt.text'. If a string is provided, than consider metadata with field name input_field. + :param output_field: field for writing the computed output in metadata. Will default to write to utterance metadata with name 'openness'. + :param input_filter: a boolean function of signature `input_filter(utterance, aux_input)`. attributes will only be computed for utterances where `input_filter` returns `True`. By default, will always return `True`, meaning that attributes will be computed for all utterances. + :param verbosity: frequency at which to print status messages when computing attributes. + """ + + def __init__( + self, + obj_type="utterance", + output_field="openness_score", + input_field=None, + input_filter=None, + verbosity=10000, + ): + if input_filter: + if len(signature(input_filter).parameters) == 1: + self.input_filter = lambda utt: input_filter(utt) + else: + self.input_filter = input_filter + else: + self.input_filter = lambda utt: True + self.obj_type = obj_type + self.input_field = input_field + self.output_field = output_field + self.verbosity = verbosity + self.open = ["what", "do", "does", "wonder", "think", "you", "why", "how", "should", "your"] + self.closed = [ + "I", + "right", + "know", + "can", + "or", + "much", + "many", + "long", + "have", + "where", + "when", + "who", + "which", + "yes", + "yeah", + "no", + ] + + def _print_output(self, i): + return (self.verbosity > 0) and (i > 0) and (i % self.verbosity == 0) + + def find_last_question(self, text): + # we return the last question + sent = sent_tokenize(text) + for sent_tok in reversed(sent): + if sent_tok[-1] == "?": + return sent_tok + # no question in this utterance + return "" + + def rule_score_for_utterance(self, text): + score = 0 + for el in text: + if el in self.closed: + score -= 1 + if el in self.open: + score += 1 + return score + + def transform(self, corpus: Corpus) -> Corpus: + """ + Score the amount of "openness_score" and annotate in the corresponding object metadata fields. + + :param corpus: Corpus + :return: the corpus + """ + if self.obj_type == "utterance": + total = len(list(corpus.iter_utterances())) + + for idx, utterance in enumerate(corpus.iter_utterances()): + if self._print_output(idx): + print(f"%03d/%03d {self.obj_type} processed" % (idx, total)) + if not self.input_filter(utterance): + continue + if self.input_field is None: + text_entry = utterance.text + elif isinstance(self.input_field, str): + text_entry = utterance.meta(self.input_field) + if text_entry is None: + continue + + sent = self.find_last_question(text_entry.lower()) + tok = word_tokenize(sent) + catch = self.rule_score_for_utterance(tok) + + utterance.add_meta(self.output_field, catch) + + elif self.obj_type == "conversation": + total = len(list(corpus.iter_conversations())) + + for idx, convo in enumerate(corpus.iter_conversations()): + if self._print_output(idx): + print(f"%03d/%03d {self.obj_type} processed" % (idx, total)) + + if not self.input_filter(convo): + continue + + if self.input_field is None: + utt_lst = convo.get_utterance_ids() + text_entry = " ".join([corpus.get_utterance(x).text for x in utt_lst]) + elif isinstance(self.input_field, str): + text_entry = convo.meta(self.input_field) + if text_entry is None: + continue + + sent = self.find_last_question(text_entry.lower()) + tok = word_tokenize(sent) + catch = self.rule_score_for_utterance(tok) + + convo.add_meta(self.output_field, catch) + + elif self.obj_type == "speaker": + total = len(list(corpus.iter_speakers())) + + for idx, sp in enumerate(corpus.iter_speakers()): + if self._print_output(idx): + print(f"%03d/%03d {self.obj_type} processed" % (idx, total)) + + if not self.input_filter(sp): + continue + + if self.input_field is None: + utt_lst = sp.get_utterance_ids() + text_entry = " ".join([corpus.get_utterance(x).text for x in utt_lst]) + elif isinstance(self.input_field, str): + text_entry = sp.meta(self.input_field) + if text_entry is None: + continue + + sent = self.find_last_question(text_entry.lower()) + tok = word_tokenize(sent) + catch = self.rule_score_for_utterance(tok) + + sp.add_meta(self.output_field, catch) + + else: + raise KeyError("obj_type must be utterance, conversation, or speaker") + + if self.verbosity > 0: + print(f"%03d/%03d {self.obj_type} processed" % (total, total)) + return corpus diff --git a/convokit/openness_rule/pull_request.md b/convokit/openness_rule/pull_request.md new file mode 100644 index 00000000..ba6f5720 --- /dev/null +++ b/convokit/openness_rule/pull_request.md @@ -0,0 +1,20 @@ +### Description + +Feature: This PR introduces a new transformer that scores utterances with questions (0 otherwise). + +The rule-based method checks to see if there are keywords associated with "open" or "closed" and adds or subtracts a point respectively. The list of keywords associated with questions is finite. This list includes the wh-questions previously explained through Pomerantz. For example, what, why, and how are all considered indicators that a question is "more open." While who, where, when, and which are all considered "more closed." + +This takes from both the distinction that Robinson and Rackstraw make, but also Dohrenwend's idea that closed questions consist of the (1) selection question, (2) yes-no question, and (3) identification question. + +Another notable category of indicator words are words that invoke opinion such as "wonder," "think," or "you." These tokens come from the human annotation tasks. We also relegate "yeah," "yes," and "no" as indicators of Dohrenwend's yes-no questions and rhetorical questions. + +When checking against the utterance, we only score the last question. This is due to the fact that when asked a series of questions, respondents will usually answer the last one posed. This also helps us avoid scoring multiple questions in one utterance. + +### Motivation and Context +We seek to examine the phenomena of question "openness" and how it might change throughout a conversation. + +### How Has This Been Tested? +We ran sanity checks through identifying a few utterances in a corpus (CANDOR) and seeing if the score performs as expected one the transformer is applied. + +### Other information: +N/A