edsl 0.1.38__py3-none-any.whl → 0.1.38.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edsl/Base.py +34 -63
- edsl/BaseDiff.py +7 -7
- edsl/__init__.py +1 -2
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +11 -23
- edsl/agents/AgentList.py +23 -86
- edsl/agents/Invigilator.py +7 -18
- edsl/agents/InvigilatorBase.py +19 -0
- edsl/agents/PromptConstructor.py +4 -5
- edsl/auto/SurveyCreatorPipeline.py +1 -1
- edsl/auto/utilities.py +1 -1
- edsl/base/Base.py +13 -3
- edsl/config.py +0 -8
- edsl/conjure/AgentConstructionMixin.py +160 -0
- edsl/conjure/Conjure.py +62 -0
- edsl/conjure/InputData.py +659 -0
- edsl/conjure/InputDataCSV.py +48 -0
- edsl/conjure/InputDataMixinQuestionStats.py +182 -0
- edsl/conjure/InputDataPyRead.py +91 -0
- edsl/conjure/InputDataSPSS.py +8 -0
- edsl/conjure/InputDataStata.py +8 -0
- edsl/conjure/QuestionOptionMixin.py +76 -0
- edsl/conjure/QuestionTypeMixin.py +23 -0
- edsl/conjure/RawQuestion.py +65 -0
- edsl/conjure/SurveyResponses.py +7 -0
- edsl/conjure/__init__.py +9 -0
- edsl/conjure/examples/placeholder.txt +0 -0
- edsl/{utilities → conjure}/naming_utilities.py +1 -1
- edsl/conjure/utilities.py +201 -0
- edsl/coop/coop.py +7 -77
- edsl/data/Cache.py +17 -45
- edsl/data/CacheEntry.py +3 -8
- edsl/data/RemoteCacheSync.py +19 -0
- edsl/enums.py +0 -2
- edsl/exceptions/agents.py +0 -4
- edsl/inference_services/GoogleService.py +15 -7
- edsl/inference_services/registry.py +0 -2
- edsl/jobs/Jobs.py +559 -110
- edsl/jobs/buckets/TokenBucket.py +0 -3
- edsl/jobs/interviews/Interview.py +7 -7
- edsl/jobs/runners/JobsRunnerAsyncio.py +28 -156
- edsl/jobs/runners/JobsRunnerStatus.py +196 -194
- edsl/jobs/tasks/TaskHistory.py +19 -27
- edsl/language_models/LanguageModel.py +90 -52
- edsl/language_models/ModelList.py +14 -67
- edsl/language_models/registry.py +4 -57
- edsl/notebooks/Notebook.py +8 -7
- edsl/prompts/Prompt.py +3 -8
- edsl/questions/QuestionBase.py +30 -38
- edsl/questions/QuestionBaseGenMixin.py +1 -1
- edsl/questions/QuestionBasePromptsMixin.py +17 -0
- edsl/questions/QuestionExtract.py +4 -3
- edsl/questions/QuestionFunctional.py +3 -10
- edsl/questions/derived/QuestionTopK.py +0 -2
- edsl/questions/question_registry.py +6 -36
- edsl/results/Dataset.py +15 -146
- edsl/results/DatasetExportMixin.py +217 -231
- edsl/results/DatasetTree.py +4 -134
- edsl/results/Result.py +16 -31
- edsl/results/Results.py +65 -159
- edsl/scenarios/FileStore.py +13 -187
- edsl/scenarios/Scenario.py +18 -73
- edsl/scenarios/ScenarioList.py +76 -251
- edsl/surveys/MemoryPlan.py +1 -1
- edsl/surveys/Rule.py +5 -1
- edsl/surveys/RuleCollection.py +1 -1
- edsl/surveys/Survey.py +19 -25
- edsl/surveys/SurveyFlowVisualizationMixin.py +9 -67
- edsl/surveys/instructions/ChangeInstruction.py +7 -9
- edsl/surveys/instructions/Instruction.py +7 -21
- edsl/templates/error_reporting/interview_details.html +3 -3
- edsl/templates/error_reporting/interviews.html +9 -18
- edsl/utilities/utilities.py +0 -15
- {edsl-0.1.38.dist-info → edsl-0.1.38.dev1.dist-info}/METADATA +1 -2
- {edsl-0.1.38.dist-info → edsl-0.1.38.dev1.dist-info}/RECORD +77 -71
- edsl/exceptions/cache.py +0 -5
- edsl/inference_services/PerplexityService.py +0 -163
- edsl/jobs/JobsChecks.py +0 -147
- edsl/jobs/JobsPrompts.py +0 -268
- edsl/jobs/JobsRemoteInferenceHandler.py +0 -239
- edsl/results/CSSParameterizer.py +0 -108
- edsl/results/TableDisplay.py +0 -198
- edsl/results/table_display.css +0 -78
- edsl/scenarios/ScenarioJoin.py +0 -127
- {edsl-0.1.38.dist-info → edsl-0.1.38.dev1.dist-info}/LICENSE +0 -0
- {edsl-0.1.38.dist-info → edsl-0.1.38.dev1.dist-info}/WHEEL +0 -0
edsl/results/DatasetTree.py
CHANGED
@@ -1,18 +1,10 @@
|
|
1
|
-
from typing import Dict, List, Any, Optional
|
1
|
+
from typing import Dict, List, Any, Optional
|
2
2
|
from docx import Document
|
3
3
|
from docx.shared import Inches, Pt
|
4
4
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
5
5
|
from docx.enum.style import WD_STYLE_TYPE
|
6
6
|
|
7
7
|
|
8
|
-
def is_hashable(v):
|
9
|
-
try:
|
10
|
-
hash(v)
|
11
|
-
return True
|
12
|
-
except TypeError:
|
13
|
-
return False
|
14
|
-
|
15
|
-
|
16
8
|
class TreeNode:
|
17
9
|
def __init__(self, key=None, value=None):
|
18
10
|
self.key = key
|
@@ -21,21 +13,16 @@ class TreeNode:
|
|
21
13
|
|
22
14
|
|
23
15
|
class Tree:
|
24
|
-
def __init__(self, data: "Dataset"
|
16
|
+
def __init__(self, data: "Dataset"):
|
25
17
|
d = {}
|
26
18
|
for entry in data:
|
27
19
|
d.update(entry)
|
28
20
|
self.data = d
|
29
21
|
self.root = None
|
30
22
|
|
31
|
-
self.node_order = node_order
|
32
|
-
|
33
|
-
self.construct_tree(node_order)
|
34
|
-
|
35
23
|
def unique_values_by_keys(self) -> dict:
|
36
24
|
unique_values = {}
|
37
|
-
for key,
|
38
|
-
values = [v if is_hashable(v) else str(v) for v in raw_values]
|
25
|
+
for key, values in self.data.items():
|
39
26
|
unique_values[key] = list(set(values))
|
40
27
|
return unique_values
|
41
28
|
|
@@ -58,25 +45,15 @@ class Tree:
|
|
58
45
|
current = self.root
|
59
46
|
for level in node_order[:-1]:
|
60
47
|
value = self.data[level][i]
|
61
|
-
if not is_hashable(value):
|
62
|
-
value = str(value)
|
63
48
|
if value not in current.children:
|
64
49
|
current.children[value] = TreeNode(key=level, value=value)
|
65
50
|
current = current.children[value]
|
66
51
|
|
67
52
|
leaf_key = node_order[-1]
|
68
53
|
leaf_value = self.data[leaf_key][i]
|
69
|
-
if not is_hashable(leaf_value):
|
70
|
-
leaf_value = str(leaf_value)
|
71
54
|
if leaf_value not in current.children:
|
72
55
|
current.children[leaf_value] = TreeNode(key=leaf_key, value=leaf_value)
|
73
56
|
|
74
|
-
def __repr__(self):
|
75
|
-
if self.node_order is not None:
|
76
|
-
return f"Tree(Dataset({self.data}), node_order={self.node_order})"
|
77
|
-
else:
|
78
|
-
return f"Tree(Dataset({self.data}))"
|
79
|
-
|
80
57
|
def print_tree(
|
81
58
|
self, node: Optional[TreeNode] = None, level: int = 0, print_keys: bool = False
|
82
59
|
):
|
@@ -94,10 +71,7 @@ class Tree:
|
|
94
71
|
for child in node.children.values():
|
95
72
|
self.print_tree(child, level + 1, print_keys)
|
96
73
|
|
97
|
-
def to_docx(self, filename:
|
98
|
-
if filename is None:
|
99
|
-
filename = "tree_structure.docx"
|
100
|
-
|
74
|
+
def to_docx(self, filename: str):
|
101
75
|
doc = Document()
|
102
76
|
|
103
77
|
# Create styles for headings
|
@@ -116,110 +90,6 @@ class Tree:
|
|
116
90
|
|
117
91
|
self._add_to_docx(doc, self.root, 0)
|
118
92
|
doc.save(filename)
|
119
|
-
from edsl.utilities.utilities import file_notice
|
120
|
-
|
121
|
-
file_notice(filename)
|
122
|
-
|
123
|
-
def _repr_html_(self):
|
124
|
-
"""Returns an interactive HTML representation of the tree with collapsible sections."""
|
125
|
-
|
126
|
-
# Generate a unique ID for this tree instance
|
127
|
-
import uuid
|
128
|
-
|
129
|
-
tree_id = f"tree_{uuid.uuid4().hex[:8]}"
|
130
|
-
|
131
|
-
styles = f"""
|
132
|
-
<div class="{tree_id}">
|
133
|
-
<style>
|
134
|
-
.{tree_id} details {{
|
135
|
-
margin-left: 20px;
|
136
|
-
}}
|
137
|
-
.{tree_id} summary {{
|
138
|
-
cursor: pointer;
|
139
|
-
margin: 2px 0;
|
140
|
-
}}
|
141
|
-
.{tree_id} .value {{
|
142
|
-
font-family: monospace;
|
143
|
-
background: #f5f5f5;
|
144
|
-
padding: 2px 6px;
|
145
|
-
border-radius: 3px;
|
146
|
-
margin: 1px 0;
|
147
|
-
}}
|
148
|
-
.{tree_id} .key {{
|
149
|
-
color: #666;
|
150
|
-
font-style: italic;
|
151
|
-
}}
|
152
|
-
</style>
|
153
|
-
"""
|
154
|
-
|
155
|
-
def node_to_html(node, level=0, print_keys=True):
|
156
|
-
if node is None:
|
157
|
-
return "Tree has not been constructed yet."
|
158
|
-
|
159
|
-
html = []
|
160
|
-
|
161
|
-
if node.value is not None:
|
162
|
-
# Create the node content
|
163
|
-
content = []
|
164
|
-
if print_keys and node.key is not None:
|
165
|
-
content.append(f'<span class="key">{node.key}: </span>')
|
166
|
-
content.append(f'<span class="value">{node.value}</span>')
|
167
|
-
content_html = "".join(content)
|
168
|
-
|
169
|
-
if node.children:
|
170
|
-
# Node with children
|
171
|
-
html.append(f'<details {"open" if level < 1 else ""}>')
|
172
|
-
html.append(f"<summary>{content_html}</summary>")
|
173
|
-
for child in node.children.values():
|
174
|
-
html.append(node_to_html(child, level + 1, print_keys))
|
175
|
-
html.append("</details>")
|
176
|
-
else:
|
177
|
-
# Leaf node
|
178
|
-
html.append(f"<div>{content_html}</div>")
|
179
|
-
else:
|
180
|
-
# Root node with no value
|
181
|
-
if node.children:
|
182
|
-
for child in node.children.values():
|
183
|
-
html.append(node_to_html(child, level, print_keys))
|
184
|
-
|
185
|
-
return "\n".join(html)
|
186
|
-
|
187
|
-
tree_html = node_to_html(self.root)
|
188
|
-
return f"{styles}{tree_html}</div>"
|
189
|
-
|
190
|
-
# def _repr_html_(self):
|
191
|
-
# """Returns an HTML representation of the tree, following the same logic as print_tree."""
|
192
|
-
# styles = """
|
193
|
-
# <style>
|
194
|
-
# .tree-container {
|
195
|
-
# font-family: monospace;
|
196
|
-
# white-space: pre;
|
197
|
-
# margin: 10px;
|
198
|
-
# }
|
199
|
-
# </style>
|
200
|
-
# """
|
201
|
-
|
202
|
-
# def node_to_html(node, level=0, print_keys=False):
|
203
|
-
# if node is None:
|
204
|
-
# node = self.root
|
205
|
-
# if node is None:
|
206
|
-
# return "Tree has not been constructed yet."
|
207
|
-
|
208
|
-
# html = []
|
209
|
-
# if node.value is not None:
|
210
|
-
# indent = " " * 2 * level # Using for HTML spaces
|
211
|
-
# if print_keys and node.key is not None:
|
212
|
-
# html.append(f"{indent}{node.key}: {node.value}<br>")
|
213
|
-
# else:
|
214
|
-
# html.append(f"{indent}{node.value}<br>")
|
215
|
-
|
216
|
-
# for child in node.children.values():
|
217
|
-
# html.append(node_to_html(child, level + 1, print_keys))
|
218
|
-
|
219
|
-
# return "".join(html)
|
220
|
-
|
221
|
-
# tree_html = node_to_html(self.root)
|
222
|
-
# return f'<div class="tree-container">{tree_html}</div>{styles}'
|
223
93
|
|
224
94
|
def _add_to_docx(self, doc, node: TreeNode, level: int):
|
225
95
|
if node.value is not None:
|
edsl/results/Result.py
CHANGED
@@ -137,15 +137,6 @@ class Result(Base, UserDict):
|
|
137
137
|
self._combined_dict = None
|
138
138
|
self._problem_keys = None
|
139
139
|
|
140
|
-
def _repr_html_(self):
|
141
|
-
# d = self.to_dict(add_edsl_version=False)
|
142
|
-
d = self.to_dict(add_edsl_version=False)
|
143
|
-
data = [[k, v] for k, v in d.items()]
|
144
|
-
from tabulate import tabulate
|
145
|
-
|
146
|
-
table = str(tabulate(data, headers=["keys", "values"], tablefmt="html"))
|
147
|
-
return f"<pre>{table}</pre>"
|
148
|
-
|
149
140
|
###############
|
150
141
|
# Used in Results
|
151
142
|
###############
|
@@ -333,24 +324,14 @@ class Result(Base, UserDict):
|
|
333
324
|
###############
|
334
325
|
# Serialization
|
335
326
|
###############
|
336
|
-
def
|
337
|
-
"""Return a dictionary representation of the Result object.
|
338
|
-
|
339
|
-
>>> r = Result.example()
|
340
|
-
>>> r.to_dict()['scenario']
|
341
|
-
{'period': 'morning', 'edsl_version': '...', 'edsl_class_name': 'Scenario'}
|
342
|
-
"""
|
343
|
-
|
344
|
-
def convert_value(value, add_edsl_version=True):
|
345
|
-
if hasattr(value, "to_dict"):
|
346
|
-
return value.to_dict(add_edsl_version=add_edsl_version)
|
347
|
-
else:
|
348
|
-
return value
|
349
|
-
|
327
|
+
def _to_dict(self) -> dict[str, Any]:
|
328
|
+
"""Return a dictionary representation of the Result object."""
|
350
329
|
d = {}
|
351
330
|
for key, value in self.items():
|
352
|
-
|
353
|
-
|
331
|
+
if hasattr(value, "to_dict"):
|
332
|
+
d[key] = value.to_dict()
|
333
|
+
else:
|
334
|
+
d[key] = value
|
354
335
|
if key == "prompt":
|
355
336
|
new_prompt_dict = {}
|
356
337
|
for prompt_name, prompt_obj in value.items():
|
@@ -360,19 +341,23 @@ class Result(Base, UserDict):
|
|
360
341
|
else prompt_obj.to_dict()
|
361
342
|
)
|
362
343
|
d[key] = new_prompt_dict
|
363
|
-
|
364
|
-
from edsl import __version__
|
344
|
+
return d
|
365
345
|
|
366
|
-
|
367
|
-
|
346
|
+
@add_edsl_version
|
347
|
+
def to_dict(self) -> dict[str, Any]:
|
348
|
+
"""Return a dictionary representation of the Result object.
|
368
349
|
|
369
|
-
|
350
|
+
>>> r = Result.example()
|
351
|
+
>>> r.to_dict()['scenario']
|
352
|
+
{'period': 'morning', 'edsl_version': '...', 'edsl_class_name': 'Scenario'}
|
353
|
+
"""
|
354
|
+
return self._to_dict()
|
370
355
|
|
371
356
|
def __hash__(self):
|
372
357
|
"""Return a hash of the Result object."""
|
373
358
|
from edsl.utilities.utilities import dict_hash
|
374
359
|
|
375
|
-
return dict_hash(self.
|
360
|
+
return dict_hash(self._to_dict())
|
376
361
|
|
377
362
|
@classmethod
|
378
363
|
@remove_edsl_version
|
edsl/results/Results.py
CHANGED
@@ -32,7 +32,7 @@ from edsl.results.ResultsDBMixin import ResultsDBMixin
|
|
32
32
|
from edsl.results.ResultsGGMixin import ResultsGGMixin
|
33
33
|
from edsl.results.ResultsFetchMixin import ResultsFetchMixin
|
34
34
|
|
35
|
-
from edsl.utilities.decorators import remove_edsl_version
|
35
|
+
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
36
36
|
from edsl.utilities.utilities import dict_hash
|
37
37
|
|
38
38
|
|
@@ -46,9 +46,6 @@ class Mixins(
|
|
46
46
|
ResultsGGMixin,
|
47
47
|
ResultsToolsMixin,
|
48
48
|
):
|
49
|
-
def long(self):
|
50
|
-
return self.table().long()
|
51
|
-
|
52
49
|
def print_long(self, max_rows: int = None) -> None:
|
53
50
|
"""Print the results in long format.
|
54
51
|
|
@@ -76,8 +73,6 @@ class Results(UserList, Mixins, Base):
|
|
76
73
|
It also has a list of created_columns, which are columns that have been created with `mutate` and are not part of the original data.
|
77
74
|
"""
|
78
75
|
|
79
|
-
__documentation__ = "https://docs.expectedparrot.com/en/latest/results.html"
|
80
|
-
|
81
76
|
known_data_types = [
|
82
77
|
"answer",
|
83
78
|
"scenario",
|
@@ -126,55 +121,13 @@ class Results(UserList, Mixins, Base):
|
|
126
121
|
if hasattr(self, "_add_output_functions"):
|
127
122
|
self._add_output_functions()
|
128
123
|
|
129
|
-
def _summary(self) -> dict:
|
130
|
-
import reprlib
|
131
|
-
|
132
|
-
# import yaml
|
133
|
-
|
134
|
-
d = {
|
135
|
-
"EDSL Class": "Results",
|
136
|
-
# "docs_url": self.__documentation__,
|
137
|
-
"# of agents": len(set(self.agents)),
|
138
|
-
"# of distinct models": len(set(self.models)),
|
139
|
-
"# of observations": len(self),
|
140
|
-
"# Scenarios": len(set(self.scenarios)),
|
141
|
-
"Survey Length (# questions)": len(self.survey),
|
142
|
-
"Survey question names": reprlib.repr(self.survey.question_names),
|
143
|
-
"Object hash": hash(self),
|
144
|
-
}
|
145
|
-
return d
|
146
|
-
|
147
|
-
def compute_job_cost(self, include_cached_responses_in_cost=False) -> float:
|
148
|
-
"""
|
149
|
-
Computes the cost of a completed job in USD.
|
150
|
-
"""
|
151
|
-
total_cost = 0
|
152
|
-
for result in self:
|
153
|
-
for key in result.raw_model_response:
|
154
|
-
if key.endswith("_cost"):
|
155
|
-
result_cost = result.raw_model_response[key]
|
156
|
-
|
157
|
-
question_name = key.removesuffix("_cost")
|
158
|
-
cache_used = result.cache_used_dict[question_name]
|
159
|
-
|
160
|
-
if isinstance(result_cost, (int, float)):
|
161
|
-
if include_cached_responses_in_cost:
|
162
|
-
total_cost += result_cost
|
163
|
-
elif not include_cached_responses_in_cost and not cache_used:
|
164
|
-
total_cost += result_cost
|
165
|
-
|
166
|
-
return total_cost
|
167
|
-
|
168
124
|
def leaves(self):
|
169
125
|
leaves = []
|
170
126
|
for result in self:
|
171
127
|
leaves.extend(result.leaves())
|
172
128
|
return leaves
|
173
129
|
|
174
|
-
def tree(
|
175
|
-
return self.to_scenario_list().tree(node_list)
|
176
|
-
|
177
|
-
def interactive_tree(
|
130
|
+
def tree(
|
178
131
|
self,
|
179
132
|
fold_attributes: Optional[List[str]] = None,
|
180
133
|
drop: Optional[List[str]] = None,
|
@@ -307,102 +260,26 @@ class Results(UserList, Mixins, Base):
|
|
307
260
|
|
308
261
|
return f"Results(data = {reprlib.repr(self.data)}, survey = {repr(self.survey)}, created_columns = {self.created_columns})"
|
309
262
|
|
310
|
-
def table(
|
311
|
-
self,
|
312
|
-
# selector_string: Optional[str] = "*.*",
|
313
|
-
*fields,
|
314
|
-
tablefmt: Optional[str] = None,
|
315
|
-
pretty_labels: Optional[dict] = None,
|
316
|
-
print_parameters: Optional[dict] = None,
|
317
|
-
):
|
318
|
-
new_fields = []
|
319
|
-
for field in fields:
|
320
|
-
if "." in field:
|
321
|
-
data_type, key = field.split(".")
|
322
|
-
if data_type not in self.known_data_types:
|
323
|
-
raise ResultsInvalidNameError(
|
324
|
-
f"{data_type} is not a valid data type. Must be in {self.known_data_types}"
|
325
|
-
)
|
326
|
-
if key == "*":
|
327
|
-
for k in self._data_type_to_keys[data_type]:
|
328
|
-
new_fields.append(k)
|
329
|
-
else:
|
330
|
-
if key not in self._key_to_data_type:
|
331
|
-
raise ResultsColumnNotFoundError(
|
332
|
-
f"{key} is not a valid key. Must be in {self._key_to_data_type}"
|
333
|
-
)
|
334
|
-
new_fields.append(key)
|
335
|
-
else:
|
336
|
-
new_fields.append(field)
|
337
|
-
|
338
|
-
return (
|
339
|
-
self.to_scenario_list()
|
340
|
-
.to_dataset()
|
341
|
-
.table(
|
342
|
-
*new_fields,
|
343
|
-
tablefmt=tablefmt,
|
344
|
-
pretty_labels=pretty_labels,
|
345
|
-
print_parameters=print_parameters,
|
346
|
-
)
|
347
|
-
)
|
348
|
-
# return (
|
349
|
-
# self.select(f"{selector_string}")
|
350
|
-
# .to_scenario_list()
|
351
|
-
# .table(*fields, tablefmt=tablefmt)
|
352
|
-
# )
|
353
|
-
|
354
263
|
def _repr_html_(self) -> str:
|
355
|
-
|
356
|
-
from edsl import Scenario
|
357
|
-
|
358
|
-
footer = f"<a href={self.__documentation__}>(docs)</a>"
|
264
|
+
from IPython.display import HTML
|
359
265
|
|
360
|
-
|
361
|
-
|
362
|
-
return td._repr_html_() + footer
|
266
|
+
json_str = json.dumps(self.to_dict()["data"], indent=4)
|
267
|
+
return f"<pre>{json_str}</pre>"
|
363
268
|
|
364
|
-
def
|
365
|
-
self,
|
366
|
-
sort=False,
|
367
|
-
add_edsl_version=False,
|
368
|
-
include_cache=False,
|
369
|
-
include_task_history=False,
|
370
|
-
) -> dict[str, Any]:
|
269
|
+
def _to_dict(self, sort=False):
|
371
270
|
from edsl.data.Cache import Cache
|
372
271
|
|
373
272
|
if sort:
|
374
273
|
data = sorted([result for result in self.data], key=lambda x: hash(x))
|
375
274
|
else:
|
376
275
|
data = [result for result in self.data]
|
377
|
-
|
378
|
-
|
379
|
-
"
|
380
|
-
result.to_dict(add_edsl_version=add_edsl_version) for result in data
|
381
|
-
],
|
382
|
-
"survey": self.survey.to_dict(add_edsl_version=add_edsl_version),
|
276
|
+
return {
|
277
|
+
"data": [result.to_dict() for result in data],
|
278
|
+
"survey": self.survey.to_dict(),
|
383
279
|
"created_columns": self.created_columns,
|
280
|
+
"cache": Cache() if not hasattr(self, "cache") else self.cache.to_dict(),
|
281
|
+
"task_history": self.task_history.to_dict(),
|
384
282
|
}
|
385
|
-
if include_cache:
|
386
|
-
d.update(
|
387
|
-
{
|
388
|
-
"cache": (
|
389
|
-
Cache()
|
390
|
-
if not hasattr(self, "cache")
|
391
|
-
else self.cache.to_dict(add_edsl_version=add_edsl_version)
|
392
|
-
)
|
393
|
-
}
|
394
|
-
)
|
395
|
-
|
396
|
-
if self.task_history.has_unfixed_exceptions or include_task_history:
|
397
|
-
d.update({"task_history": self.task_history.to_dict()})
|
398
|
-
|
399
|
-
if add_edsl_version:
|
400
|
-
from edsl import __version__
|
401
|
-
|
402
|
-
d["edsl_version"] = __version__
|
403
|
-
d["edsl_class_name"] = "Results"
|
404
|
-
|
405
|
-
return d
|
406
283
|
|
407
284
|
def compare(self, other_results):
|
408
285
|
"""
|
@@ -425,8 +302,22 @@ class Results(UserList, Mixins, Base):
|
|
425
302
|
def has_unfixed_exceptions(self):
|
426
303
|
return self.task_history.has_unfixed_exceptions
|
427
304
|
|
305
|
+
@add_edsl_version
|
306
|
+
def to_dict(self) -> dict[str, Any]:
|
307
|
+
"""Convert the Results object to a dictionary.
|
308
|
+
|
309
|
+
The dictionary can be quite large, as it includes all of the data in the Results object.
|
310
|
+
|
311
|
+
Example: Illustrating just the keys of the dictionary.
|
312
|
+
|
313
|
+
>>> r = Results.example()
|
314
|
+
>>> r.to_dict().keys()
|
315
|
+
dict_keys(['data', 'survey', 'created_columns', 'cache', 'task_history', 'edsl_version', 'edsl_class_name'])
|
316
|
+
"""
|
317
|
+
return self._to_dict()
|
318
|
+
|
428
319
|
def __hash__(self) -> int:
|
429
|
-
return dict_hash(self.
|
320
|
+
return dict_hash(self._to_dict(sort=True))
|
430
321
|
|
431
322
|
@property
|
432
323
|
def hashes(self) -> set:
|
@@ -484,11 +375,7 @@ class Results(UserList, Mixins, Base):
|
|
484
375
|
cache=(
|
485
376
|
Cache.from_dict(data.get("cache")) if "cache" in data else Cache()
|
486
377
|
),
|
487
|
-
task_history=(
|
488
|
-
TaskHistory.from_dict(data.get("task_history"))
|
489
|
-
if "task_history" in data
|
490
|
-
else TaskHistory(interviews=[])
|
491
|
-
),
|
378
|
+
task_history=TaskHistory.from_dict(data.get("task_history")),
|
492
379
|
)
|
493
380
|
except Exception as e:
|
494
381
|
raise ResultsDeserializationError(f"Error in Results.from_dict: {e}")
|
@@ -988,19 +875,31 @@ class Results(UserList, Mixins, Base):
|
|
988
875
|
|
989
876
|
>>> r = Results.example()
|
990
877
|
>>> r.sort_by('how_feeling', reverse=False).select('how_feeling').print()
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
878
|
+
┏━━━━━━━━━━━━━━┓
|
879
|
+
┃ answer ┃
|
880
|
+
┃ .how_feeling ┃
|
881
|
+
┡━━━━━━━━━━━━━━┩
|
882
|
+
│ Great │
|
883
|
+
├──────────────┤
|
884
|
+
│ OK │
|
885
|
+
├──────────────┤
|
886
|
+
│ OK │
|
887
|
+
├──────────────┤
|
888
|
+
│ Terrible │
|
889
|
+
└──────────────┘
|
997
890
|
>>> r.sort_by('how_feeling', reverse=True).select('how_feeling').print()
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
891
|
+
┏━━━━━━━━━━━━━━┓
|
892
|
+
┃ answer ┃
|
893
|
+
┃ .how_feeling ┃
|
894
|
+
┡━━━━━━━━━━━━━━┩
|
895
|
+
│ Terrible │
|
896
|
+
├──────────────┤
|
897
|
+
│ OK │
|
898
|
+
├──────────────┤
|
899
|
+
│ OK │
|
900
|
+
├──────────────┤
|
901
|
+
│ Great │
|
902
|
+
└──────────────┘
|
1004
903
|
"""
|
1005
904
|
|
1006
905
|
def to_numeric_if_possible(v):
|
@@ -1033,9 +932,12 @@ class Results(UserList, Mixins, Base):
|
|
1033
932
|
|
1034
933
|
>>> r = Results.example()
|
1035
934
|
>>> r.filter("how_feeling == 'Great'").select('how_feeling').print()
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
935
|
+
┏━━━━━━━━━━━━━━┓
|
936
|
+
┃ answer ┃
|
937
|
+
┃ .how_feeling ┃
|
938
|
+
┡━━━━━━━━━━━━━━┩
|
939
|
+
│ Great │
|
940
|
+
└──────────────┘
|
1039
941
|
|
1040
942
|
Example usage: Using an OR operator in the filter expression.
|
1041
943
|
|
@@ -1046,10 +948,14 @@ class Results(UserList, Mixins, Base):
|
|
1046
948
|
...
|
1047
949
|
|
1048
950
|
>>> r.filter("how_feeling == 'Great' or how_feeling == 'Terrible'").select('how_feeling').print()
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
951
|
+
┏━━━━━━━━━━━━━━┓
|
952
|
+
┃ answer ┃
|
953
|
+
┃ .how_feeling ┃
|
954
|
+
┡━━━━━━━━━━━━━━┩
|
955
|
+
│ Great │
|
956
|
+
├──────────────┤
|
957
|
+
│ Terrible │
|
958
|
+
└──────────────┘
|
1053
959
|
"""
|
1054
960
|
|
1055
961
|
def has_single_equals(string):
|