edsl 0.1.29.dev3__py3-none-any.whl → 0.1.30__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 +18 -18
- edsl/__init__.py +23 -23
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +79 -41
- edsl/agents/AgentList.py +26 -26
- edsl/agents/Invigilator.py +19 -2
- edsl/agents/InvigilatorBase.py +15 -10
- edsl/agents/PromptConstructionMixin.py +342 -100
- edsl/agents/descriptors.py +2 -1
- edsl/base/Base.py +289 -0
- edsl/config.py +2 -1
- edsl/conjure/InputData.py +39 -8
- edsl/conversation/car_buying.py +1 -1
- edsl/coop/coop.py +187 -150
- edsl/coop/utils.py +43 -75
- edsl/data/Cache.py +41 -18
- edsl/data/CacheEntry.py +6 -7
- edsl/data/SQLiteDict.py +11 -3
- edsl/data_transfer_models.py +4 -0
- edsl/jobs/Answers.py +15 -1
- edsl/jobs/Jobs.py +108 -49
- edsl/jobs/buckets/ModelBuckets.py +14 -2
- edsl/jobs/buckets/TokenBucket.py +32 -5
- edsl/jobs/interviews/Interview.py +99 -79
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +19 -24
- edsl/jobs/runners/JobsRunnerAsyncio.py +16 -16
- edsl/jobs/tasks/QuestionTaskCreator.py +10 -6
- edsl/jobs/tasks/TaskHistory.py +4 -3
- edsl/language_models/LanguageModel.py +17 -17
- edsl/language_models/ModelList.py +1 -1
- edsl/language_models/repair.py +8 -7
- edsl/notebooks/Notebook.py +47 -10
- edsl/prompts/Prompt.py +31 -19
- edsl/questions/QuestionBase.py +38 -13
- edsl/questions/QuestionBudget.py +5 -6
- edsl/questions/QuestionCheckBox.py +7 -3
- edsl/questions/QuestionExtract.py +5 -3
- edsl/questions/QuestionFreeText.py +7 -5
- edsl/questions/QuestionFunctional.py +34 -5
- edsl/questions/QuestionList.py +3 -4
- edsl/questions/QuestionMultipleChoice.py +68 -12
- edsl/questions/QuestionNumerical.py +4 -3
- edsl/questions/QuestionRank.py +5 -3
- edsl/questions/__init__.py +4 -3
- edsl/questions/descriptors.py +46 -4
- edsl/questions/question_registry.py +20 -31
- edsl/questions/settings.py +1 -1
- edsl/results/Dataset.py +31 -0
- edsl/results/DatasetExportMixin.py +570 -0
- edsl/results/Result.py +66 -70
- edsl/results/Results.py +160 -68
- edsl/results/ResultsDBMixin.py +7 -3
- edsl/results/ResultsExportMixin.py +22 -537
- edsl/results/ResultsGGMixin.py +3 -3
- edsl/results/ResultsToolsMixin.py +5 -5
- edsl/scenarios/FileStore.py +299 -0
- edsl/scenarios/Scenario.py +16 -24
- edsl/scenarios/ScenarioList.py +42 -17
- edsl/scenarios/ScenarioListExportMixin.py +32 -0
- edsl/scenarios/ScenarioListPdfMixin.py +2 -1
- edsl/scenarios/__init__.py +1 -0
- edsl/study/Study.py +8 -16
- edsl/surveys/MemoryPlan.py +11 -4
- edsl/surveys/Survey.py +88 -17
- edsl/surveys/SurveyExportMixin.py +4 -2
- edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
- edsl/tools/plotting.py +4 -2
- edsl/utilities/__init__.py +21 -21
- edsl/utilities/interface.py +66 -45
- edsl/utilities/utilities.py +11 -13
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/METADATA +11 -10
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/RECORD +74 -71
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/WHEEL +1 -1
- edsl-0.1.29.dev3.dist-info/entry_points.txt +0 -3
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/LICENSE +0 -0
edsl/results/Result.py
CHANGED
@@ -3,16 +3,7 @@ from __future__ import annotations
|
|
3
3
|
from collections import UserDict
|
4
4
|
from typing import Any, Type, Callable, Optional
|
5
5
|
from collections import UserDict
|
6
|
-
|
7
|
-
from rich.table import Table
|
8
|
-
|
9
|
-
from IPython.display import display
|
10
|
-
|
11
|
-
from edsl.agents import Agent
|
12
|
-
from edsl.language_models import LanguageModel
|
13
|
-
from edsl.scenarios import Scenario
|
14
6
|
from edsl.Base import Base
|
15
|
-
from edsl.prompts import Prompt
|
16
7
|
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
17
8
|
|
18
9
|
|
@@ -21,6 +12,8 @@ class PromptDict(UserDict):
|
|
21
12
|
|
22
13
|
def rich_print(self):
|
23
14
|
"""Display an object as a table."""
|
15
|
+
from rich.table import Table
|
16
|
+
|
24
17
|
table = Table(title="")
|
25
18
|
table.add_column("Attribute", style="bold")
|
26
19
|
table.add_column("Value")
|
@@ -71,9 +64,9 @@ class Result(Base, UserDict):
|
|
71
64
|
|
72
65
|
def __init__(
|
73
66
|
self,
|
74
|
-
agent: Agent,
|
75
|
-
scenario: Scenario,
|
76
|
-
model: Type[LanguageModel],
|
67
|
+
agent: "Agent",
|
68
|
+
scenario: "Scenario",
|
69
|
+
model: Type["LanguageModel"],
|
77
70
|
iteration: int,
|
78
71
|
answer: str,
|
79
72
|
prompt: dict[str, str] = None,
|
@@ -133,6 +126,9 @@ class Result(Base, UserDict):
|
|
133
126
|
self.survey = survey
|
134
127
|
self.question_to_attributes = question_to_attributes
|
135
128
|
|
129
|
+
self._combined_dict = None
|
130
|
+
self._problem_keys = None
|
131
|
+
|
136
132
|
###############
|
137
133
|
# Used in Results
|
138
134
|
###############
|
@@ -171,25 +167,64 @@ class Result(Base, UserDict):
|
|
171
167
|
"answer": self.answer,
|
172
168
|
"prompt": self.prompt,
|
173
169
|
"raw_model_response": self.raw_model_response,
|
174
|
-
"iteration": {"iteration": self.iteration},
|
170
|
+
# "iteration": {"iteration": self.iteration},
|
175
171
|
"question_text": question_text_dict,
|
176
172
|
"question_options": question_options_dict,
|
177
173
|
"question_type": question_type_dict,
|
178
174
|
"comment": comments_dict,
|
179
175
|
}
|
180
176
|
|
177
|
+
def check_expression(self, expression) -> None:
|
178
|
+
for key in self.problem_keys:
|
179
|
+
if key in expression and not key + "." in expression:
|
180
|
+
raise ValueError(
|
181
|
+
f"Key by iself {key} is problematic. Use the full key {key + '.' + key} name instead."
|
182
|
+
)
|
183
|
+
return None
|
184
|
+
|
181
185
|
def code(self):
|
182
186
|
"""Return a string of code that can be used to recreate the Result object."""
|
183
187
|
raise NotImplementedError
|
184
188
|
|
185
189
|
@property
|
186
|
-
def
|
187
|
-
"""Return a
|
190
|
+
def problem_keys(self):
|
191
|
+
"""Return a list of keys that are problematic."""
|
192
|
+
return self._problem_keys
|
193
|
+
|
194
|
+
def _compute_combined_dict_and_problem_keys(self) -> None:
|
188
195
|
combined = {}
|
196
|
+
problem_keys = []
|
189
197
|
for key, sub_dict in self.sub_dicts.items():
|
190
198
|
combined.update(sub_dict)
|
199
|
+
# in some cases, the sub_dict might have keys that conflict with the main dict
|
200
|
+
if key in combined:
|
201
|
+
# The key is already in the combined dict
|
202
|
+
problem_keys = problem_keys + [key]
|
203
|
+
|
191
204
|
combined.update({key: sub_dict})
|
192
|
-
|
205
|
+
# I *think* this allows us to do do things like "answer.how_feelling" i.e., that the evaluator can use
|
206
|
+
# dot notation to access the subdicts.
|
207
|
+
self._combined_dict = combined
|
208
|
+
self._problem_keys = problem_keys
|
209
|
+
|
210
|
+
@property
|
211
|
+
def combined_dict(self) -> dict[str, Any]:
|
212
|
+
"""Return a dictionary that includes all sub_dicts, but also puts the key-value pairs in each sub_dict as a key_value pair in the combined dictionary.
|
213
|
+
|
214
|
+
>>> r = Result.example()
|
215
|
+
>>> r.combined_dict['how_feeling']
|
216
|
+
'OK'
|
217
|
+
"""
|
218
|
+
if self._combined_dict is None or self._problem_keys is None:
|
219
|
+
self._compute_combined_dict_and_problem_keys()
|
220
|
+
return self._combined_dict
|
221
|
+
|
222
|
+
@property
|
223
|
+
def problem_keys(self):
|
224
|
+
"""Return a list of keys that are problematic."""
|
225
|
+
if self._combined_dict is None or self._problem_keys is None:
|
226
|
+
self._compute_combined_dict_and_problem_keys()
|
227
|
+
return self._problem_keys
|
193
228
|
|
194
229
|
def get_value(self, data_type: str, key: str) -> Any:
|
195
230
|
"""Return the value for a given data type and key.
|
@@ -233,7 +268,13 @@ class Result(Base, UserDict):
|
|
233
268
|
return Result.from_dict(self.to_dict())
|
234
269
|
|
235
270
|
def __eq__(self, other) -> bool:
|
236
|
-
"""Return True if the Result object is equal to another Result object.
|
271
|
+
"""Return True if the Result object is equal to another Result object.
|
272
|
+
|
273
|
+
>>> r = Result.example()
|
274
|
+
>>> r == r
|
275
|
+
True
|
276
|
+
|
277
|
+
"""
|
237
278
|
return self.to_dict() == other.to_dict()
|
238
279
|
|
239
280
|
###############
|
@@ -278,6 +319,12 @@ class Result(Base, UserDict):
|
|
278
319
|
@remove_edsl_version
|
279
320
|
def from_dict(self, json_dict: dict) -> Result:
|
280
321
|
"""Return a Result object from a dictionary representation."""
|
322
|
+
|
323
|
+
from edsl import Agent
|
324
|
+
from edsl import Scenario
|
325
|
+
from edsl.language_models.LanguageModel import LanguageModel
|
326
|
+
from edsl.prompts.Prompt import Prompt
|
327
|
+
|
281
328
|
prompt_data = json_dict.get("prompt", {})
|
282
329
|
prompt_d = {}
|
283
330
|
for prompt_name, prompt_obj in prompt_data.items():
|
@@ -301,6 +348,7 @@ class Result(Base, UserDict):
|
|
301
348
|
"""Display an object as a table."""
|
302
349
|
# from edsl.utilities import print_dict_with_rich
|
303
350
|
from rich import print
|
351
|
+
from rich.table import Table
|
304
352
|
|
305
353
|
table = Table(title="Result")
|
306
354
|
table.add_column("Attribute", style="bold")
|
@@ -325,7 +373,7 @@ class Result(Base, UserDict):
|
|
325
373
|
@classmethod
|
326
374
|
def example(cls):
|
327
375
|
"""Return an example Result object."""
|
328
|
-
from edsl.results import Results
|
376
|
+
from edsl.results.Results import Results
|
329
377
|
|
330
378
|
return Results.example()[0]
|
331
379
|
|
@@ -350,59 +398,7 @@ class Result(Base, UserDict):
|
|
350
398
|
return scoring_function(**params)
|
351
399
|
|
352
400
|
|
353
|
-
def main():
|
354
|
-
"""Run the main function."""
|
355
|
-
from edsl.results.Result import Result
|
356
|
-
import json
|
357
|
-
|
358
|
-
print("Being imported")
|
359
|
-
json_string = """
|
360
|
-
{
|
361
|
-
"agent": {
|
362
|
-
"traits": {
|
363
|
-
"status": "Unhappy"
|
364
|
-
}
|
365
|
-
},
|
366
|
-
"scenario": {
|
367
|
-
"period": "morning"
|
368
|
-
},
|
369
|
-
"model": {
|
370
|
-
"model": "gpt-3.5-turbo",
|
371
|
-
"parameters": {
|
372
|
-
"temperature": 0.5,
|
373
|
-
"max_tokens": 1000,
|
374
|
-
"top_p": 1,
|
375
|
-
"frequency_penalty": 0,
|
376
|
-
"presence_penalty": 0,
|
377
|
-
"use_cache": true
|
378
|
-
}
|
379
|
-
},
|
380
|
-
"iteration": 0,
|
381
|
-
"answer": {
|
382
|
-
"how_feeling": "Bad"
|
383
|
-
},
|
384
|
-
"prompt": {"how_feeling_user_prompt": "How are you feeling today?", "how_feeling_system_prompt": "Answer the question"}
|
385
|
-
}
|
386
|
-
"""
|
387
|
-
|
388
|
-
result = Result.from_dict(json.loads(json_string))
|
389
|
-
|
390
|
-
result.sub_dicts
|
391
|
-
assert result.combined_dict["how_feeling"] == "Bad"
|
392
|
-
|
393
|
-
result.combined_dict
|
394
|
-
assert result.get_value("answer", "how_feeling") == "Bad"
|
395
|
-
|
396
|
-
result.key_to_data_type
|
397
|
-
print(result)
|
398
|
-
|
399
|
-
assert result == result.copy()
|
400
|
-
|
401
|
-
result.to_dict()
|
402
|
-
|
403
|
-
|
404
401
|
if __name__ == "__main__":
|
405
|
-
# print(Result.example())
|
406
402
|
import doctest
|
407
403
|
|
408
404
|
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
edsl/results/Results.py
CHANGED
@@ -5,16 +5,10 @@ It is not typically instantiated directly, but is returned by the run method of
|
|
5
5
|
|
6
6
|
from __future__ import annotations
|
7
7
|
import json
|
8
|
-
import hashlib
|
9
8
|
import random
|
10
9
|
from collections import UserList, defaultdict
|
11
10
|
from typing import Optional, Callable, Any, Type, Union, List
|
12
11
|
|
13
|
-
from pygments import highlight
|
14
|
-
from pygments.lexers import JsonLexer
|
15
|
-
from pygments.formatters import HtmlFormatter
|
16
|
-
from IPython.display import HTML
|
17
|
-
|
18
12
|
from simpleeval import EvalWithCompoundTypes
|
19
13
|
|
20
14
|
from edsl.exceptions.results import (
|
@@ -24,29 +18,17 @@ from edsl.exceptions.results import (
|
|
24
18
|
ResultsMutateError,
|
25
19
|
ResultsFilterError,
|
26
20
|
)
|
27
|
-
|
28
|
-
from edsl.language_models.LanguageModel import LanguageModel
|
29
|
-
from edsl.results.Dataset import Dataset
|
30
|
-
from edsl.results.Result import Result
|
21
|
+
|
31
22
|
from edsl.results.ResultsExportMixin import ResultsExportMixin
|
32
|
-
from edsl.scenarios import Scenario
|
33
|
-
|
34
|
-
# from edsl.scenarios.ScenarioList import ScenarioList
|
35
|
-
from edsl.surveys import Survey
|
36
|
-
from edsl.data.Cache import Cache
|
37
|
-
from edsl.utilities import (
|
38
|
-
is_valid_variable_name,
|
39
|
-
shorten_string,
|
40
|
-
)
|
41
|
-
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
42
|
-
from edsl.utilities.utilities import dict_hash
|
43
23
|
from edsl.results.ResultsToolsMixin import ResultsToolsMixin
|
44
|
-
|
45
24
|
from edsl.results.ResultsDBMixin import ResultsDBMixin
|
46
25
|
from edsl.results.ResultsGGMixin import ResultsGGMixin
|
26
|
+
from edsl.results.ResultsFetchMixin import ResultsFetchMixin
|
27
|
+
|
28
|
+
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
29
|
+
from edsl.utilities.utilities import dict_hash
|
47
30
|
|
48
31
|
from edsl.Base import Base
|
49
|
-
from edsl.results.ResultsFetchMixin import ResultsFetchMixin
|
50
32
|
|
51
33
|
|
52
34
|
class Mixins(
|
@@ -56,7 +38,22 @@ class Mixins(
|
|
56
38
|
ResultsGGMixin,
|
57
39
|
ResultsToolsMixin,
|
58
40
|
):
|
59
|
-
|
41
|
+
def print_long(self, max_rows=None) -> None:
|
42
|
+
"""Print the results in long format.
|
43
|
+
|
44
|
+
>>> from edsl.results import Results
|
45
|
+
>>> r = Results.example()
|
46
|
+
>>> r.select('how_feeling').print_long(max_rows = 2)
|
47
|
+
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━┓
|
48
|
+
┃ Result index ┃ Key ┃ Value ┃
|
49
|
+
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━┩
|
50
|
+
│ 0 │ how_feeling │ OK │
|
51
|
+
│ 1 │ how_feeling │ Great │
|
52
|
+
└──────────────┴─────────────┴───────┘
|
53
|
+
"""
|
54
|
+
from edsl.utilities.interface import print_results_long
|
55
|
+
|
56
|
+
print_results_long(self, max_rows=max_rows)
|
60
57
|
|
61
58
|
|
62
59
|
class Results(UserList, Mixins, Base):
|
@@ -84,10 +81,10 @@ class Results(UserList, Mixins, Base):
|
|
84
81
|
|
85
82
|
def __init__(
|
86
83
|
self,
|
87
|
-
survey: Optional[Survey] = None,
|
88
|
-
data: Optional[list[Result]] = None,
|
84
|
+
survey: Optional["Survey"] = None,
|
85
|
+
data: Optional[list["Result"]] = None,
|
89
86
|
created_columns: Optional[list[str]] = None,
|
90
|
-
cache: Optional[Cache] = None,
|
87
|
+
cache: Optional["Cache"] = None,
|
91
88
|
job_uuid: Optional[str] = None,
|
92
89
|
total_results: Optional[int] = None,
|
93
90
|
):
|
@@ -100,6 +97,8 @@ class Results(UserList, Mixins, Base):
|
|
100
97
|
:param total_results: An integer representing the total number of results.
|
101
98
|
"""
|
102
99
|
super().__init__(data)
|
100
|
+
from edsl.data.Cache import Cache
|
101
|
+
|
103
102
|
self.survey = survey
|
104
103
|
self.created_columns = created_columns or []
|
105
104
|
self._job_uuid = job_uuid
|
@@ -125,6 +124,10 @@ class Results(UserList, Mixins, Base):
|
|
125
124
|
raise TypeError("Invalid argument type")
|
126
125
|
|
127
126
|
def _update_results(self) -> None:
|
127
|
+
from edsl import Agent, Scenario
|
128
|
+
from edsl.language_models import LanguageModel
|
129
|
+
from edsl.results import Result
|
130
|
+
|
128
131
|
if self._job_uuid and len(self.data) < self._total_results:
|
129
132
|
results = [
|
130
133
|
Result(
|
@@ -168,7 +171,13 @@ class Results(UserList, Mixins, Base):
|
|
168
171
|
return f"Results(data = {self.data}, survey = {repr(self.survey)}, created_columns = {self.created_columns})"
|
169
172
|
|
170
173
|
def _repr_html_(self) -> str:
|
174
|
+
from IPython.display import HTML
|
175
|
+
|
171
176
|
json_str = json.dumps(self.to_dict()["data"], indent=4)
|
177
|
+
from pygments import highlight
|
178
|
+
from pygments.lexers import JsonLexer
|
179
|
+
from pygments.formatters import HtmlFormatter
|
180
|
+
|
172
181
|
formatted_json = highlight(
|
173
182
|
json_str,
|
174
183
|
JsonLexer(),
|
@@ -177,6 +186,8 @@ class Results(UserList, Mixins, Base):
|
|
177
186
|
return HTML(formatted_json).data
|
178
187
|
|
179
188
|
def _to_dict(self, sort=False):
|
189
|
+
from edsl.data.Cache import Cache
|
190
|
+
|
180
191
|
if sort:
|
181
192
|
data = sorted([result for result in self.data], key=lambda x: hash(x))
|
182
193
|
else:
|
@@ -226,6 +237,31 @@ class Results(UserList, Mixins, Base):
|
|
226
237
|
def hashes(self) -> set:
|
227
238
|
return set(hash(result) for result in self.data)
|
228
239
|
|
240
|
+
def sample(self, n: int) -> "Results":
|
241
|
+
"""Return a random sample of the results.
|
242
|
+
|
243
|
+
:param n: The number of samples to return.
|
244
|
+
|
245
|
+
>>> from edsl.results import Results
|
246
|
+
>>> r = Results.example()
|
247
|
+
>>> len(r.sample(2))
|
248
|
+
2
|
249
|
+
"""
|
250
|
+
indices = None
|
251
|
+
|
252
|
+
for entry in self:
|
253
|
+
key, values = list(entry.items())[0]
|
254
|
+
if indices is None: # gets the indices for the first time
|
255
|
+
indices = list(range(len(values)))
|
256
|
+
sampled_indices = random.sample(indices, n)
|
257
|
+
if n > len(indices):
|
258
|
+
raise ValueError(
|
259
|
+
f"Cannot sample {n} items from a list of length {len(indices)}."
|
260
|
+
)
|
261
|
+
entry[key] = [values[i] for i in sampled_indices]
|
262
|
+
|
263
|
+
return self
|
264
|
+
|
229
265
|
@classmethod
|
230
266
|
@remove_edsl_version
|
231
267
|
def from_dict(cls, data: dict[str, Any]) -> Results:
|
@@ -241,12 +277,21 @@ class Results(UserList, Mixins, Base):
|
|
241
277
|
>>> r == r2
|
242
278
|
True
|
243
279
|
"""
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
280
|
+
from edsl import Survey, Cache
|
281
|
+
from edsl.results.Result import Result
|
282
|
+
|
283
|
+
try:
|
284
|
+
results = cls(
|
285
|
+
survey=Survey.from_dict(data["survey"]),
|
286
|
+
data=[Result.from_dict(r) for r in data["data"]],
|
287
|
+
created_columns=data.get("created_columns", None),
|
288
|
+
cache=(
|
289
|
+
Cache.from_dict(data.get("cache")) if "cache" in data else Cache()
|
290
|
+
),
|
291
|
+
)
|
292
|
+
except Exception as e:
|
293
|
+
print(e)
|
294
|
+
# breakpoint()
|
250
295
|
return results
|
251
296
|
|
252
297
|
######################
|
@@ -313,6 +358,8 @@ class Results(UserList, Mixins, Base):
|
|
313
358
|
>>> r.answer_keys
|
314
359
|
{'how_feeling': 'How are you this {{ period }}?', 'how_feeling_yesterday': 'How were you feeling yesterday {{ period }}?'}
|
315
360
|
"""
|
361
|
+
from edsl.utilities.utilities import shorten_string
|
362
|
+
|
316
363
|
if not self.survey:
|
317
364
|
raise Exception("Survey is not defined so no answer keys are available.")
|
318
365
|
|
@@ -327,7 +374,7 @@ class Results(UserList, Mixins, Base):
|
|
327
374
|
return sorted_dict
|
328
375
|
|
329
376
|
@property
|
330
|
-
def agents(self) -> AgentList:
|
377
|
+
def agents(self) -> "AgentList":
|
331
378
|
"""Return a list of all of the agents in the Results.
|
332
379
|
|
333
380
|
Example:
|
@@ -336,10 +383,12 @@ class Results(UserList, Mixins, Base):
|
|
336
383
|
>>> r.agents
|
337
384
|
AgentList([Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Sad'}), Agent(traits = {'status': 'Sad'})])
|
338
385
|
"""
|
386
|
+
from edsl import AgentList
|
387
|
+
|
339
388
|
return AgentList([r.agent for r in self.data])
|
340
389
|
|
341
390
|
@property
|
342
|
-
def models(self) -> list[Type[LanguageModel]]:
|
391
|
+
def models(self) -> list[Type["LanguageModel"]]:
|
343
392
|
"""Return a list of all of the models in the Results.
|
344
393
|
|
345
394
|
Example:
|
@@ -461,7 +510,7 @@ class Results(UserList, Mixins, Base):
|
|
461
510
|
)
|
462
511
|
return data_type, key
|
463
512
|
|
464
|
-
def first(self) -> Result:
|
513
|
+
def first(self) -> "Result":
|
465
514
|
"""Return the first observation in the results.
|
466
515
|
|
467
516
|
Example:
|
@@ -555,6 +604,38 @@ class Results(UserList, Mixins, Base):
|
|
555
604
|
self = self.add_column(key, values)
|
556
605
|
return self
|
557
606
|
|
607
|
+
@staticmethod
|
608
|
+
def _create_evaluator(
|
609
|
+
result: Result, functions_dict: Optional[dict] = None
|
610
|
+
) -> EvalWithCompoundTypes:
|
611
|
+
"""Create an evaluator for the expression.
|
612
|
+
|
613
|
+
>>> from unittest.mock import Mock
|
614
|
+
>>> result = Mock()
|
615
|
+
>>> result.combined_dict = {'how_feeling': 'OK'}
|
616
|
+
|
617
|
+
>>> evaluator = Results._create_evaluator(result = result, functions_dict = {})
|
618
|
+
>>> evaluator.eval("how_feeling == 'OK'")
|
619
|
+
True
|
620
|
+
|
621
|
+
>>> result.combined_dict = {'answer': {'how_feeling': 'OK'}}
|
622
|
+
>>> evaluator = Results._create_evaluator(result = result, functions_dict = {})
|
623
|
+
>>> evaluator.eval("answer.how_feeling== 'OK'")
|
624
|
+
True
|
625
|
+
|
626
|
+
Note that you need to refer to the answer dictionary in the expression.
|
627
|
+
|
628
|
+
>>> evaluator.eval("how_feeling== 'OK'")
|
629
|
+
Traceback (most recent call last):
|
630
|
+
...
|
631
|
+
simpleeval.NameNotDefined: 'how_feeling' is not defined for expression 'how_feeling== 'OK''
|
632
|
+
"""
|
633
|
+
if functions_dict is None:
|
634
|
+
functions_dict = {}
|
635
|
+
return EvalWithCompoundTypes(
|
636
|
+
names=result.combined_dict, functions=functions_dict
|
637
|
+
)
|
638
|
+
|
558
639
|
def mutate(
|
559
640
|
self, new_var_string: str, functions_dict: Optional[dict] = None
|
560
641
|
) -> Results:
|
@@ -579,19 +660,16 @@ class Results(UserList, Mixins, Base):
|
|
579
660
|
)
|
580
661
|
raw_var_name, expression = new_var_string.split("=", 1)
|
581
662
|
var_name = raw_var_name.strip()
|
663
|
+
from edsl.utilities.utilities import is_valid_variable_name
|
664
|
+
|
582
665
|
if not is_valid_variable_name(var_name):
|
583
666
|
raise ResultsInvalidNameError(f"{var_name} is not a valid variable name.")
|
584
667
|
|
585
668
|
# create the evaluator
|
586
669
|
functions_dict = functions_dict or {}
|
587
670
|
|
588
|
-
def
|
589
|
-
|
590
|
-
names=result.combined_dict, functions=functions_dict
|
591
|
-
)
|
592
|
-
|
593
|
-
def new_result(old_result: Result, var_name: str) -> Result:
|
594
|
-
evaluator = create_evaluator(old_result)
|
671
|
+
def new_result(old_result: "Result", var_name: str) -> "Result":
|
672
|
+
evaluator = self._create_evaluator(old_result, functions_dict)
|
595
673
|
value = evaluator.eval(expression)
|
596
674
|
new_result = old_result.copy()
|
597
675
|
new_result["answer"][var_name] = value
|
@@ -680,7 +758,7 @@ class Results(UserList, Mixins, Base):
|
|
680
758
|
|
681
759
|
return Results(survey=self.survey, data=new_data, created_columns=None)
|
682
760
|
|
683
|
-
def select(self, *columns: Union[str, list[str]]) -> Dataset:
|
761
|
+
def select(self, *columns: Union[str, list[str]]) -> "Dataset":
|
684
762
|
"""
|
685
763
|
Select data from the results and format it.
|
686
764
|
|
@@ -691,7 +769,11 @@ class Results(UserList, Mixins, Base):
|
|
691
769
|
>>> results = Results.example()
|
692
770
|
>>> results.select('how_feeling')
|
693
771
|
Dataset([{'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}])
|
772
|
+
|
773
|
+
>>> results.select('how_feeling', 'model', 'how_feeling')
|
774
|
+
Dataset([{'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}, {'model.model': ['gpt-4-1106-preview', 'gpt-4-1106-preview', 'gpt-4-1106-preview', 'gpt-4-1106-preview']}, {'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}])
|
694
775
|
"""
|
776
|
+
|
695
777
|
if len(self) == 0:
|
696
778
|
raise Exception("No data to select from---the Results object is empty.")
|
697
779
|
|
@@ -747,9 +829,19 @@ class Results(UserList, Mixins, Base):
|
|
747
829
|
# Return the index of this key in the list_of_keys
|
748
830
|
return items_in_order.index(single_key)
|
749
831
|
|
750
|
-
sorted(new_data, key=sort_by_key_order)
|
832
|
+
# sorted(new_data, key=sort_by_key_order)
|
833
|
+
from edsl.results.Dataset import Dataset
|
834
|
+
|
835
|
+
sorted_new_data = []
|
751
836
|
|
752
|
-
|
837
|
+
# WORKS but slow
|
838
|
+
for key in items_in_order:
|
839
|
+
for d in new_data:
|
840
|
+
if key in d:
|
841
|
+
sorted_new_data.append(d)
|
842
|
+
break
|
843
|
+
|
844
|
+
return Dataset(sorted_new_data)
|
753
845
|
|
754
846
|
def sort_by(self, *columns: str, reverse: bool = False) -> Results:
|
755
847
|
import warnings
|
@@ -864,29 +956,29 @@ class Results(UserList, Mixins, Base):
|
|
864
956
|
"You must use '==' instead of '=' in the filter expression."
|
865
957
|
)
|
866
958
|
|
867
|
-
def create_evaluator(result):
|
868
|
-
"""Create an evaluator for the given result.
|
869
|
-
The 'combined_dict' is a mapping of all values for that Result object.
|
870
|
-
"""
|
871
|
-
return EvalWithCompoundTypes(names=result.combined_dict)
|
872
|
-
|
873
959
|
try:
|
874
960
|
# iterates through all the results and evaluates the expression
|
875
|
-
new_data = [
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
961
|
+
new_data = []
|
962
|
+
for result in self.data:
|
963
|
+
evaluator = self._create_evaluator(result)
|
964
|
+
result.check_expression(expression) # check expression
|
965
|
+
if evaluator.eval(expression):
|
966
|
+
new_data.append(result)
|
967
|
+
|
968
|
+
except ValueError as e:
|
969
|
+
raise ResultsFilterError(
|
970
|
+
f"Error in filter. Exception:{e}",
|
971
|
+
f"The expression you provided was: {expression}",
|
972
|
+
"See https://docs.expectedparrot.com/en/latest/results.html#filtering-results for more details.",
|
973
|
+
)
|
880
974
|
except Exception as e:
|
881
975
|
raise ResultsFilterError(
|
882
|
-
f"""Error in filter. Exception:{e}.
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
See https://docs.expectedparrot.com/en/latest/results.html#filtering-results for more details.
|
889
|
-
"""
|
976
|
+
f"""Error in filter. Exception:{e}.""",
|
977
|
+
f"""The expression you provided was: {expression}.""",
|
978
|
+
"""Please make sure that the expression is a valid Python expression that evaluates to a boolean.""",
|
979
|
+
"""For example, 'how_feeling == "Great"' is a valid expression, as is 'how_feeling in ["Great", "Terrible"]'., """,
|
980
|
+
"""However, 'how_feeling = "Great"' is not a valid expression.""",
|
981
|
+
"""See https://docs.expectedparrot.com/en/latest/results.html#filtering-results for more details.""",
|
890
982
|
)
|
891
983
|
|
892
984
|
if len(new_data) == 0:
|
@@ -897,7 +989,7 @@ class Results(UserList, Mixins, Base):
|
|
897
989
|
return Results(survey=self.survey, data=new_data, created_columns=None)
|
898
990
|
|
899
991
|
@classmethod
|
900
|
-
def example(cls, debug: bool = False) -> Results:
|
992
|
+
def example(cls, debug: bool = False, randomize: bool = False) -> Results:
|
901
993
|
"""Return an example `Results` object.
|
902
994
|
|
903
995
|
Example usage:
|
@@ -906,11 +998,11 @@ class Results(UserList, Mixins, Base):
|
|
906
998
|
|
907
999
|
:param debug: if False, uses actual API calls
|
908
1000
|
"""
|
909
|
-
from edsl.jobs import Jobs
|
1001
|
+
from edsl.jobs.Jobs import Jobs
|
910
1002
|
from edsl.data.Cache import Cache
|
911
1003
|
|
912
1004
|
c = Cache()
|
913
|
-
job = Jobs.example()
|
1005
|
+
job = Jobs.example(randomize=randomize)
|
914
1006
|
results = job.run(cache=c, debug=debug)
|
915
1007
|
return results
|
916
1008
|
|
edsl/results/ResultsDBMixin.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
"""Mixin for working with SQLite respresentation of a 'Results' object."""
|
2
2
|
|
3
|
-
import pandas as pd
|
4
3
|
import sqlite3
|
5
|
-
from sqlalchemy import create_engine
|
6
4
|
from enum import Enum
|
7
5
|
from typing import Literal, Union, Optional
|
8
6
|
|
@@ -92,6 +90,8 @@ class ResultsDBMixin:
|
|
92
90
|
conn.commit()
|
93
91
|
return conn
|
94
92
|
elif shape == SQLDataShape.WIDE:
|
93
|
+
from sqlalchemy import create_engine
|
94
|
+
|
95
95
|
engine = create_engine("sqlite:///:memory:")
|
96
96
|
df = self.to_pandas(remove_prefix=remove_prefix)
|
97
97
|
df.to_sql("self", engine, index=False, if_exists="replace")
|
@@ -121,7 +121,7 @@ class ResultsDBMixin:
|
|
121
121
|
to_list=False,
|
122
122
|
to_latex=False,
|
123
123
|
filename: Optional[str] = None,
|
124
|
-
) -> Union[pd.DataFrame, str]:
|
124
|
+
) -> Union["pd.DataFrame", str]:
|
125
125
|
"""Execute a SQL query and return the results as a DataFrame.
|
126
126
|
|
127
127
|
:param query: The SQL query to execute
|
@@ -151,6 +151,8 @@ class ResultsDBMixin:
|
|
151
151
|
2 Terrible
|
152
152
|
3 OK
|
153
153
|
"""
|
154
|
+
import pandas as pd
|
155
|
+
|
154
156
|
shape_enum = self._get_shape_enum(shape)
|
155
157
|
|
156
158
|
conn = self._db(shape=shape_enum, remove_prefix=remove_prefix)
|
@@ -205,6 +207,8 @@ class ResultsDBMixin:
|
|
205
207
|
...
|
206
208
|
<BLANKLINE>
|
207
209
|
"""
|
210
|
+
import pandas as pd
|
211
|
+
|
208
212
|
shape_enum = self._get_shape_enum(shape)
|
209
213
|
conn = self._db(shape=shape_enum, remove_prefix=remove_prefix)
|
210
214
|
|