edsl 0.1.37.dev5__py3-none-any.whl → 0.1.38__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 +63 -34
- edsl/BaseDiff.py +7 -7
- edsl/__init__.py +2 -1
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +23 -11
- edsl/agents/AgentList.py +86 -23
- edsl/agents/Invigilator.py +18 -7
- edsl/agents/InvigilatorBase.py +0 -19
- edsl/agents/PromptConstructor.py +5 -4
- edsl/auto/SurveyCreatorPipeline.py +1 -1
- edsl/auto/utilities.py +1 -1
- edsl/base/Base.py +3 -13
- edsl/config.py +8 -0
- edsl/coop/coop.py +89 -19
- edsl/data/Cache.py +45 -17
- edsl/data/CacheEntry.py +8 -3
- edsl/data/RemoteCacheSync.py +0 -19
- edsl/enums.py +2 -0
- edsl/exceptions/agents.py +4 -0
- edsl/exceptions/cache.py +5 -0
- edsl/inference_services/GoogleService.py +7 -15
- edsl/inference_services/PerplexityService.py +163 -0
- edsl/inference_services/registry.py +2 -0
- edsl/jobs/Jobs.py +110 -559
- edsl/jobs/JobsChecks.py +147 -0
- edsl/jobs/JobsPrompts.py +268 -0
- edsl/jobs/JobsRemoteInferenceHandler.py +239 -0
- edsl/jobs/buckets/TokenBucket.py +3 -0
- edsl/jobs/interviews/Interview.py +7 -7
- edsl/jobs/runners/JobsRunnerAsyncio.py +156 -28
- edsl/jobs/runners/JobsRunnerStatus.py +194 -196
- edsl/jobs/tasks/TaskHistory.py +27 -19
- edsl/language_models/LanguageModel.py +52 -90
- edsl/language_models/ModelList.py +67 -14
- edsl/language_models/registry.py +57 -4
- edsl/notebooks/Notebook.py +7 -8
- edsl/prompts/Prompt.py +8 -3
- edsl/questions/QuestionBase.py +38 -30
- edsl/questions/QuestionBaseGenMixin.py +1 -1
- edsl/questions/QuestionBasePromptsMixin.py +0 -17
- edsl/questions/QuestionExtract.py +3 -4
- edsl/questions/QuestionFunctional.py +10 -3
- edsl/questions/derived/QuestionTopK.py +2 -0
- edsl/questions/question_registry.py +36 -6
- edsl/results/CSSParameterizer.py +108 -0
- edsl/results/Dataset.py +146 -15
- edsl/results/DatasetExportMixin.py +231 -217
- edsl/results/DatasetTree.py +134 -4
- edsl/results/Result.py +31 -16
- edsl/results/Results.py +159 -65
- edsl/results/TableDisplay.py +198 -0
- edsl/results/table_display.css +78 -0
- edsl/scenarios/FileStore.py +187 -13
- edsl/scenarios/Scenario.py +73 -18
- edsl/scenarios/ScenarioJoin.py +127 -0
- edsl/scenarios/ScenarioList.py +251 -76
- edsl/surveys/MemoryPlan.py +1 -1
- edsl/surveys/Rule.py +1 -5
- edsl/surveys/RuleCollection.py +1 -1
- edsl/surveys/Survey.py +25 -19
- edsl/surveys/SurveyFlowVisualizationMixin.py +67 -9
- edsl/surveys/instructions/ChangeInstruction.py +9 -7
- edsl/surveys/instructions/Instruction.py +21 -7
- edsl/templates/error_reporting/interview_details.html +3 -3
- edsl/templates/error_reporting/interviews.html +18 -9
- edsl/{conjure → utilities}/naming_utilities.py +1 -1
- edsl/utilities/utilities.py +15 -0
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/METADATA +2 -1
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/RECORD +71 -77
- edsl/conjure/AgentConstructionMixin.py +0 -160
- edsl/conjure/Conjure.py +0 -62
- edsl/conjure/InputData.py +0 -659
- edsl/conjure/InputDataCSV.py +0 -48
- edsl/conjure/InputDataMixinQuestionStats.py +0 -182
- edsl/conjure/InputDataPyRead.py +0 -91
- edsl/conjure/InputDataSPSS.py +0 -8
- edsl/conjure/InputDataStata.py +0 -8
- edsl/conjure/QuestionOptionMixin.py +0 -76
- edsl/conjure/QuestionTypeMixin.py +0 -23
- edsl/conjure/RawQuestion.py +0 -65
- edsl/conjure/SurveyResponses.py +0 -7
- edsl/conjure/__init__.py +0 -9
- edsl/conjure/examples/placeholder.txt +0 -0
- edsl/conjure/utilities.py +0 -201
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/LICENSE +0 -0
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/WHEEL +0 -0
edsl/results/DatasetTree.py
CHANGED
@@ -1,10 +1,18 @@
|
|
1
|
-
from typing import Dict, List, Any, Optional
|
1
|
+
from typing import Dict, List, Any, Optional, List
|
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
|
+
|
8
16
|
class TreeNode:
|
9
17
|
def __init__(self, key=None, value=None):
|
10
18
|
self.key = key
|
@@ -13,16 +21,21 @@ class TreeNode:
|
|
13
21
|
|
14
22
|
|
15
23
|
class Tree:
|
16
|
-
def __init__(self, data: "Dataset"):
|
24
|
+
def __init__(self, data: "Dataset", node_order: Optional[List[str]] = None):
|
17
25
|
d = {}
|
18
26
|
for entry in data:
|
19
27
|
d.update(entry)
|
20
28
|
self.data = d
|
21
29
|
self.root = None
|
22
30
|
|
31
|
+
self.node_order = node_order
|
32
|
+
|
33
|
+
self.construct_tree(node_order)
|
34
|
+
|
23
35
|
def unique_values_by_keys(self) -> dict:
|
24
36
|
unique_values = {}
|
25
|
-
for key,
|
37
|
+
for key, raw_values in self.data.items():
|
38
|
+
values = [v if is_hashable(v) else str(v) for v in raw_values]
|
26
39
|
unique_values[key] = list(set(values))
|
27
40
|
return unique_values
|
28
41
|
|
@@ -45,15 +58,25 @@ class Tree:
|
|
45
58
|
current = self.root
|
46
59
|
for level in node_order[:-1]:
|
47
60
|
value = self.data[level][i]
|
61
|
+
if not is_hashable(value):
|
62
|
+
value = str(value)
|
48
63
|
if value not in current.children:
|
49
64
|
current.children[value] = TreeNode(key=level, value=value)
|
50
65
|
current = current.children[value]
|
51
66
|
|
52
67
|
leaf_key = node_order[-1]
|
53
68
|
leaf_value = self.data[leaf_key][i]
|
69
|
+
if not is_hashable(leaf_value):
|
70
|
+
leaf_value = str(leaf_value)
|
54
71
|
if leaf_value not in current.children:
|
55
72
|
current.children[leaf_value] = TreeNode(key=leaf_key, value=leaf_value)
|
56
73
|
|
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
|
+
|
57
80
|
def print_tree(
|
58
81
|
self, node: Optional[TreeNode] = None, level: int = 0, print_keys: bool = False
|
59
82
|
):
|
@@ -71,7 +94,10 @@ class Tree:
|
|
71
94
|
for child in node.children.values():
|
72
95
|
self.print_tree(child, level + 1, print_keys)
|
73
96
|
|
74
|
-
def to_docx(self, filename: str):
|
97
|
+
def to_docx(self, filename: Optional[str] = None):
|
98
|
+
if filename is None:
|
99
|
+
filename = "tree_structure.docx"
|
100
|
+
|
75
101
|
doc = Document()
|
76
102
|
|
77
103
|
# Create styles for headings
|
@@ -90,6 +116,110 @@ class Tree:
|
|
90
116
|
|
91
117
|
self._add_to_docx(doc, self.root, 0)
|
92
118
|
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}'
|
93
223
|
|
94
224
|
def _add_to_docx(self, doc, node: TreeNode, level: int):
|
95
225
|
if node.value is not None:
|
edsl/results/Result.py
CHANGED
@@ -137,6 +137,15 @@ 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
|
+
|
140
149
|
###############
|
141
150
|
# Used in Results
|
142
151
|
###############
|
@@ -324,14 +333,24 @@ class Result(Base, UserDict):
|
|
324
333
|
###############
|
325
334
|
# Serialization
|
326
335
|
###############
|
327
|
-
def
|
328
|
-
"""Return a dictionary representation of the Result object.
|
329
|
-
|
330
|
-
|
336
|
+
def to_dict(self, add_edsl_version=True) -> dict[str, Any]:
|
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):
|
331
345
|
if hasattr(value, "to_dict"):
|
332
|
-
|
346
|
+
return value.to_dict(add_edsl_version=add_edsl_version)
|
333
347
|
else:
|
334
|
-
|
348
|
+
return value
|
349
|
+
|
350
|
+
d = {}
|
351
|
+
for key, value in self.items():
|
352
|
+
d[key] = convert_value(value, add_edsl_version=add_edsl_version)
|
353
|
+
|
335
354
|
if key == "prompt":
|
336
355
|
new_prompt_dict = {}
|
337
356
|
for prompt_name, prompt_obj in value.items():
|
@@ -341,23 +360,19 @@ class Result(Base, UserDict):
|
|
341
360
|
else prompt_obj.to_dict()
|
342
361
|
)
|
343
362
|
d[key] = new_prompt_dict
|
344
|
-
|
363
|
+
if add_edsl_version:
|
364
|
+
from edsl import __version__
|
345
365
|
|
346
|
-
|
347
|
-
|
348
|
-
"""Return a dictionary representation of the Result object.
|
366
|
+
d["edsl_version"] = __version__
|
367
|
+
d["edsl_class_name"] = "Result"
|
349
368
|
|
350
|
-
|
351
|
-
>>> r.to_dict()['scenario']
|
352
|
-
{'period': 'morning', 'edsl_version': '...', 'edsl_class_name': 'Scenario'}
|
353
|
-
"""
|
354
|
-
return self._to_dict()
|
369
|
+
return d
|
355
370
|
|
356
371
|
def __hash__(self):
|
357
372
|
"""Return a hash of the Result object."""
|
358
373
|
from edsl.utilities.utilities import dict_hash
|
359
374
|
|
360
|
-
return dict_hash(self.
|
375
|
+
return dict_hash(self.to_dict(add_edsl_version=False))
|
361
376
|
|
362
377
|
@classmethod
|
363
378
|
@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
|
35
|
+
from edsl.utilities.decorators import remove_edsl_version
|
36
36
|
from edsl.utilities.utilities import dict_hash
|
37
37
|
|
38
38
|
|
@@ -46,6 +46,9 @@ class Mixins(
|
|
46
46
|
ResultsGGMixin,
|
47
47
|
ResultsToolsMixin,
|
48
48
|
):
|
49
|
+
def long(self):
|
50
|
+
return self.table().long()
|
51
|
+
|
49
52
|
def print_long(self, max_rows: int = None) -> None:
|
50
53
|
"""Print the results in long format.
|
51
54
|
|
@@ -73,6 +76,8 @@ class Results(UserList, Mixins, Base):
|
|
73
76
|
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.
|
74
77
|
"""
|
75
78
|
|
79
|
+
__documentation__ = "https://docs.expectedparrot.com/en/latest/results.html"
|
80
|
+
|
76
81
|
known_data_types = [
|
77
82
|
"answer",
|
78
83
|
"scenario",
|
@@ -121,13 +126,55 @@ class Results(UserList, Mixins, Base):
|
|
121
126
|
if hasattr(self, "_add_output_functions"):
|
122
127
|
self._add_output_functions()
|
123
128
|
|
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
|
+
|
124
168
|
def leaves(self):
|
125
169
|
leaves = []
|
126
170
|
for result in self:
|
127
171
|
leaves.extend(result.leaves())
|
128
172
|
return leaves
|
129
173
|
|
130
|
-
def tree(
|
174
|
+
def tree(self, node_list: Optional[List[str]] = None):
|
175
|
+
return self.to_scenario_list().tree(node_list)
|
176
|
+
|
177
|
+
def interactive_tree(
|
131
178
|
self,
|
132
179
|
fold_attributes: Optional[List[str]] = None,
|
133
180
|
drop: Optional[List[str]] = None,
|
@@ -260,26 +307,102 @@ class Results(UserList, Mixins, Base):
|
|
260
307
|
|
261
308
|
return f"Results(data = {reprlib.repr(self.data)}, survey = {repr(self.survey)}, created_columns = {self.created_columns})"
|
262
309
|
|
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
|
+
|
263
354
|
def _repr_html_(self) -> str:
|
264
|
-
|
355
|
+
d = self._summary()
|
356
|
+
from edsl import Scenario
|
357
|
+
|
358
|
+
footer = f"<a href={self.__documentation__}>(docs)</a>"
|
265
359
|
|
266
|
-
|
267
|
-
|
360
|
+
s = Scenario(d)
|
361
|
+
td = s.to_dataset().table(tablefmt="html")
|
362
|
+
return td._repr_html_() + footer
|
268
363
|
|
269
|
-
def
|
364
|
+
def to_dict(
|
365
|
+
self,
|
366
|
+
sort=False,
|
367
|
+
add_edsl_version=False,
|
368
|
+
include_cache=False,
|
369
|
+
include_task_history=False,
|
370
|
+
) -> dict[str, Any]:
|
270
371
|
from edsl.data.Cache import Cache
|
271
372
|
|
272
373
|
if sort:
|
273
374
|
data = sorted([result for result in self.data], key=lambda x: hash(x))
|
274
375
|
else:
|
275
376
|
data = [result for result in self.data]
|
276
|
-
|
277
|
-
|
278
|
-
"
|
377
|
+
|
378
|
+
d = {
|
379
|
+
"data": [
|
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),
|
279
383
|
"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(),
|
282
384
|
}
|
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
|
283
406
|
|
284
407
|
def compare(self, other_results):
|
285
408
|
"""
|
@@ -302,22 +425,8 @@ class Results(UserList, Mixins, Base):
|
|
302
425
|
def has_unfixed_exceptions(self):
|
303
426
|
return self.task_history.has_unfixed_exceptions
|
304
427
|
|
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
|
-
|
319
428
|
def __hash__(self) -> int:
|
320
|
-
return dict_hash(self.
|
429
|
+
return dict_hash(self.to_dict(sort=True, add_edsl_version=False))
|
321
430
|
|
322
431
|
@property
|
323
432
|
def hashes(self) -> set:
|
@@ -375,7 +484,11 @@ class Results(UserList, Mixins, Base):
|
|
375
484
|
cache=(
|
376
485
|
Cache.from_dict(data.get("cache")) if "cache" in data else Cache()
|
377
486
|
),
|
378
|
-
task_history=
|
487
|
+
task_history=(
|
488
|
+
TaskHistory.from_dict(data.get("task_history"))
|
489
|
+
if "task_history" in data
|
490
|
+
else TaskHistory(interviews=[])
|
491
|
+
),
|
379
492
|
)
|
380
493
|
except Exception as e:
|
381
494
|
raise ResultsDeserializationError(f"Error in Results.from_dict: {e}")
|
@@ -875,31 +988,19 @@ class Results(UserList, Mixins, Base):
|
|
875
988
|
|
876
989
|
>>> r = Results.example()
|
877
990
|
>>> r.sort_by('how_feeling', reverse=False).select('how_feeling').print()
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
│ OK │
|
885
|
-
├──────────────┤
|
886
|
-
│ OK │
|
887
|
-
├──────────────┤
|
888
|
-
│ Terrible │
|
889
|
-
└──────────────┘
|
991
|
+
answer.how_feeling
|
992
|
+
--------------------
|
993
|
+
Great
|
994
|
+
OK
|
995
|
+
OK
|
996
|
+
Terrible
|
890
997
|
>>> r.sort_by('how_feeling', reverse=True).select('how_feeling').print()
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
│ OK │
|
898
|
-
├──────────────┤
|
899
|
-
│ OK │
|
900
|
-
├──────────────┤
|
901
|
-
│ Great │
|
902
|
-
└──────────────┘
|
998
|
+
answer.how_feeling
|
999
|
+
--------------------
|
1000
|
+
Terrible
|
1001
|
+
OK
|
1002
|
+
OK
|
1003
|
+
Great
|
903
1004
|
"""
|
904
1005
|
|
905
1006
|
def to_numeric_if_possible(v):
|
@@ -932,12 +1033,9 @@ class Results(UserList, Mixins, Base):
|
|
932
1033
|
|
933
1034
|
>>> r = Results.example()
|
934
1035
|
>>> r.filter("how_feeling == 'Great'").select('how_feeling').print()
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
┡━━━━━━━━━━━━━━┩
|
939
|
-
│ Great │
|
940
|
-
└──────────────┘
|
1036
|
+
answer.how_feeling
|
1037
|
+
--------------------
|
1038
|
+
Great
|
941
1039
|
|
942
1040
|
Example usage: Using an OR operator in the filter expression.
|
943
1041
|
|
@@ -948,14 +1046,10 @@ class Results(UserList, Mixins, Base):
|
|
948
1046
|
...
|
949
1047
|
|
950
1048
|
>>> r.filter("how_feeling == 'Great' or how_feeling == 'Terrible'").select('how_feeling').print()
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
│ Great │
|
956
|
-
├──────────────┤
|
957
|
-
│ Terrible │
|
958
|
-
└──────────────┘
|
1049
|
+
answer.how_feeling
|
1050
|
+
--------------------
|
1051
|
+
Great
|
1052
|
+
Terrible
|
959
1053
|
"""
|
960
1054
|
|
961
1055
|
def has_single_equals(string):
|