edsl 0.1.39__py3-none-any.whl → 0.1.39.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.
Files changed (212) hide show
  1. edsl/Base.py +116 -197
  2. edsl/__init__.py +7 -15
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +147 -351
  5. edsl/agents/AgentList.py +73 -211
  6. edsl/agents/Invigilator.py +50 -101
  7. edsl/agents/InvigilatorBase.py +70 -62
  8. edsl/agents/PromptConstructor.py +225 -143
  9. edsl/agents/__init__.py +1 -0
  10. edsl/agents/prompt_helpers.py +3 -3
  11. edsl/auto/AutoStudy.py +5 -18
  12. edsl/auto/StageBase.py +40 -53
  13. edsl/auto/StageQuestions.py +1 -2
  14. edsl/auto/utilities.py +6 -0
  15. edsl/config.py +2 -22
  16. edsl/conversation/car_buying.py +1 -2
  17. edsl/coop/PriceFetcher.py +1 -1
  18. edsl/coop/coop.py +47 -125
  19. edsl/coop/utils.py +14 -14
  20. edsl/data/Cache.py +27 -45
  21. edsl/data/CacheEntry.py +15 -12
  22. edsl/data/CacheHandler.py +12 -31
  23. edsl/data/RemoteCacheSync.py +46 -154
  24. edsl/data/__init__.py +3 -4
  25. edsl/data_transfer_models.py +1 -2
  26. edsl/enums.py +0 -27
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +0 -12
  29. edsl/exceptions/questions.py +6 -24
  30. edsl/exceptions/scenarios.py +0 -7
  31. edsl/inference_services/AnthropicService.py +19 -38
  32. edsl/inference_services/AwsBedrock.py +2 -0
  33. edsl/inference_services/AzureAI.py +2 -0
  34. edsl/inference_services/GoogleService.py +12 -7
  35. edsl/inference_services/InferenceServiceABC.py +85 -18
  36. edsl/inference_services/InferenceServicesCollection.py +79 -120
  37. edsl/inference_services/MistralAIService.py +3 -0
  38. edsl/inference_services/OpenAIService.py +35 -47
  39. edsl/inference_services/PerplexityService.py +3 -0
  40. edsl/inference_services/TestService.py +10 -11
  41. edsl/inference_services/TogetherAIService.py +3 -5
  42. edsl/jobs/Answers.py +14 -1
  43. edsl/jobs/Jobs.py +431 -356
  44. edsl/jobs/JobsChecks.py +10 -35
  45. edsl/jobs/JobsPrompts.py +4 -6
  46. edsl/jobs/JobsRemoteInferenceHandler.py +133 -205
  47. edsl/jobs/buckets/BucketCollection.py +3 -44
  48. edsl/jobs/buckets/TokenBucket.py +21 -53
  49. edsl/jobs/interviews/Interview.py +408 -143
  50. edsl/jobs/runners/JobsRunnerAsyncio.py +403 -88
  51. edsl/jobs/runners/JobsRunnerStatus.py +165 -133
  52. edsl/jobs/tasks/QuestionTaskCreator.py +19 -21
  53. edsl/jobs/tasks/TaskHistory.py +18 -38
  54. edsl/jobs/tasks/task_status_enum.py +2 -0
  55. edsl/language_models/KeyLookup.py +30 -0
  56. edsl/language_models/LanguageModel.py +236 -194
  57. edsl/language_models/ModelList.py +19 -28
  58. edsl/language_models/__init__.py +2 -1
  59. edsl/language_models/registry.py +190 -0
  60. edsl/language_models/repair.py +2 -2
  61. edsl/language_models/unused/ReplicateBase.py +83 -0
  62. edsl/language_models/utilities.py +4 -5
  63. edsl/notebooks/Notebook.py +14 -19
  64. edsl/prompts/Prompt.py +39 -29
  65. edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +2 -47
  66. edsl/questions/QuestionBase.py +214 -68
  67. edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +50 -57
  68. edsl/questions/QuestionBasePromptsMixin.py +3 -7
  69. edsl/questions/QuestionBudget.py +1 -1
  70. edsl/questions/QuestionCheckBox.py +3 -3
  71. edsl/questions/QuestionExtract.py +7 -5
  72. edsl/questions/QuestionFreeText.py +3 -2
  73. edsl/questions/QuestionList.py +18 -10
  74. edsl/questions/QuestionMultipleChoice.py +23 -67
  75. edsl/questions/QuestionNumerical.py +4 -2
  76. edsl/questions/QuestionRank.py +17 -7
  77. edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +26 -40
  78. edsl/questions/SimpleAskMixin.py +3 -4
  79. edsl/questions/__init__.py +1 -2
  80. edsl/questions/derived/QuestionLinearScale.py +3 -6
  81. edsl/questions/derived/QuestionTopK.py +1 -1
  82. edsl/questions/descriptors.py +3 -17
  83. edsl/questions/question_registry.py +1 -1
  84. edsl/results/CSSParameterizer.py +1 -1
  85. edsl/results/Dataset.py +7 -170
  86. edsl/results/DatasetExportMixin.py +305 -168
  87. edsl/results/DatasetTree.py +8 -28
  88. edsl/results/Result.py +206 -298
  89. edsl/results/Results.py +131 -149
  90. edsl/results/ResultsDBMixin.py +238 -0
  91. edsl/results/ResultsExportMixin.py +0 -2
  92. edsl/results/{results_selector.py → Selector.py} +13 -23
  93. edsl/results/TableDisplay.py +171 -98
  94. edsl/results/__init__.py +1 -1
  95. edsl/scenarios/FileStore.py +239 -150
  96. edsl/scenarios/Scenario.py +193 -90
  97. edsl/scenarios/ScenarioHtmlMixin.py +3 -4
  98. edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +6 -10
  99. edsl/scenarios/ScenarioList.py +244 -415
  100. edsl/scenarios/ScenarioListExportMixin.py +7 -0
  101. edsl/scenarios/ScenarioListPdfMixin.py +37 -15
  102. edsl/scenarios/__init__.py +2 -1
  103. edsl/study/ObjectEntry.py +1 -1
  104. edsl/study/SnapShot.py +1 -1
  105. edsl/study/Study.py +12 -5
  106. edsl/surveys/Rule.py +4 -5
  107. edsl/surveys/RuleCollection.py +27 -25
  108. edsl/surveys/Survey.py +791 -270
  109. edsl/surveys/SurveyCSS.py +8 -20
  110. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +9 -11
  111. edsl/surveys/__init__.py +2 -4
  112. edsl/surveys/descriptors.py +2 -6
  113. edsl/surveys/instructions/ChangeInstruction.py +2 -1
  114. edsl/surveys/instructions/Instruction.py +13 -4
  115. edsl/surveys/instructions/InstructionCollection.py +6 -11
  116. edsl/templates/error_reporting/interview_details.html +1 -1
  117. edsl/templates/error_reporting/report.html +1 -1
  118. edsl/tools/plotting.py +1 -1
  119. edsl/utilities/utilities.py +23 -35
  120. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/METADATA +10 -12
  121. edsl-0.1.39.dev1.dist-info/RECORD +277 -0
  122. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/WHEEL +1 -1
  123. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  124. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  125. edsl/agents/question_option_processor.py +0 -172
  126. edsl/coop/CoopFunctionsMixin.py +0 -15
  127. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  128. edsl/exceptions/inference_services.py +0 -5
  129. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  130. edsl/inference_services/AvailableModelFetcher.py +0 -215
  131. edsl/inference_services/ServiceAvailability.py +0 -135
  132. edsl/inference_services/data_structures.py +0 -134
  133. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -223
  134. edsl/jobs/FetchInvigilator.py +0 -47
  135. edsl/jobs/InterviewTaskManager.py +0 -98
  136. edsl/jobs/InterviewsConstructor.py +0 -50
  137. edsl/jobs/JobsComponentConstructor.py +0 -189
  138. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  139. edsl/jobs/RequestTokenEstimator.py +0 -30
  140. edsl/jobs/async_interview_runner.py +0 -138
  141. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  142. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  143. edsl/jobs/check_survey_scenario_compatibility.py +0 -85
  144. edsl/jobs/data_structures.py +0 -120
  145. edsl/jobs/decorators.py +0 -35
  146. edsl/jobs/jobs_status_enums.py +0 -9
  147. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  148. edsl/jobs/results_exceptions_handler.py +0 -98
  149. edsl/language_models/ComputeCost.py +0 -63
  150. edsl/language_models/PriceManager.py +0 -127
  151. edsl/language_models/RawResponseHandler.py +0 -106
  152. edsl/language_models/ServiceDataSources.py +0 -0
  153. edsl/language_models/key_management/KeyLookup.py +0 -63
  154. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  155. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  156. edsl/language_models/key_management/__init__.py +0 -0
  157. edsl/language_models/key_management/models.py +0 -131
  158. edsl/language_models/model.py +0 -256
  159. edsl/notebooks/NotebookToLaTeX.py +0 -142
  160. edsl/questions/ExceptionExplainer.py +0 -77
  161. edsl/questions/HTMLQuestion.py +0 -103
  162. edsl/questions/QuestionMatrix.py +0 -265
  163. edsl/questions/data_structures.py +0 -20
  164. edsl/questions/loop_processor.py +0 -149
  165. edsl/questions/response_validator_factory.py +0 -34
  166. edsl/questions/templates/matrix/__init__.py +0 -1
  167. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  168. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  169. edsl/results/MarkdownToDocx.py +0 -122
  170. edsl/results/MarkdownToPDF.py +0 -111
  171. edsl/results/TextEditor.py +0 -50
  172. edsl/results/file_exports.py +0 -252
  173. edsl/results/smart_objects.py +0 -96
  174. edsl/results/table_data_class.py +0 -12
  175. edsl/results/table_renderers.py +0 -118
  176. edsl/scenarios/ConstructDownloadLink.py +0 -109
  177. edsl/scenarios/DocumentChunker.py +0 -102
  178. edsl/scenarios/DocxScenario.py +0 -16
  179. edsl/scenarios/PdfExtractor.py +0 -40
  180. edsl/scenarios/directory_scanner.py +0 -96
  181. edsl/scenarios/file_methods.py +0 -85
  182. edsl/scenarios/handlers/__init__.py +0 -13
  183. edsl/scenarios/handlers/csv.py +0 -49
  184. edsl/scenarios/handlers/docx.py +0 -76
  185. edsl/scenarios/handlers/html.py +0 -37
  186. edsl/scenarios/handlers/json.py +0 -111
  187. edsl/scenarios/handlers/latex.py +0 -5
  188. edsl/scenarios/handlers/md.py +0 -51
  189. edsl/scenarios/handlers/pdf.py +0 -68
  190. edsl/scenarios/handlers/png.py +0 -39
  191. edsl/scenarios/handlers/pptx.py +0 -105
  192. edsl/scenarios/handlers/py.py +0 -294
  193. edsl/scenarios/handlers/sql.py +0 -313
  194. edsl/scenarios/handlers/sqlite.py +0 -149
  195. edsl/scenarios/handlers/txt.py +0 -33
  196. edsl/scenarios/scenario_selector.py +0 -156
  197. edsl/surveys/ConstructDAG.py +0 -92
  198. edsl/surveys/EditSurvey.py +0 -221
  199. edsl/surveys/InstructionHandler.py +0 -100
  200. edsl/surveys/MemoryManagement.py +0 -72
  201. edsl/surveys/RuleManager.py +0 -172
  202. edsl/surveys/Simulator.py +0 -75
  203. edsl/surveys/SurveyToApp.py +0 -141
  204. edsl/utilities/PrettyList.py +0 -56
  205. edsl/utilities/is_notebook.py +0 -18
  206. edsl/utilities/is_valid_variable_name.py +0 -11
  207. edsl/utilities/remove_edsl_version.py +0 -24
  208. edsl-0.1.39.dist-info/RECORD +0 -358
  209. /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
  210. /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
  211. /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
  212. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/LICENSE +0 -0
edsl/results/Results.py CHANGED
@@ -9,9 +9,13 @@ import random
9
9
  from collections import UserList, defaultdict
10
10
  from typing import Optional, Callable, Any, Type, Union, List, TYPE_CHECKING
11
11
 
12
- from bisect import bisect_left
12
+ if TYPE_CHECKING:
13
+ from edsl import Survey, Cache, AgentList, ModelList, ScenarioList
14
+ from edsl.results.Result import Result
15
+ from edsl.jobs.tasks.TaskHistory import TaskHistory
16
+
17
+ from simpleeval import EvalWithCompoundTypes
13
18
 
14
- from edsl.Base import Base
15
19
  from edsl.exceptions.results import (
16
20
  ResultsError,
17
21
  ResultsBadMutationstringError,
@@ -22,27 +26,25 @@ from edsl.exceptions.results import (
22
26
  ResultsDeserializationError,
23
27
  )
24
28
 
25
- if TYPE_CHECKING:
26
- from edsl.surveys.Survey import Survey
27
- from edsl.data.Cache import Cache
28
- from edsl.agents.AgentList import AgentList
29
- from edsl.language_models.model import Model
30
- from edsl.scenarios.ScenarioList import ScenarioList
31
- from edsl.results.Result import Result
32
- from edsl.jobs.tasks.TaskHistory import TaskHistory
33
- from edsl.language_models.ModelList import ModelList
34
- from simpleeval import EvalWithCompoundTypes
35
-
36
29
  from edsl.results.ResultsExportMixin import ResultsExportMixin
30
+ from edsl.results.ResultsToolsMixin import ResultsToolsMixin
31
+ from edsl.results.ResultsDBMixin import ResultsDBMixin
37
32
  from edsl.results.ResultsGGMixin import ResultsGGMixin
38
- from edsl.results.results_fetch_mixin import ResultsFetchMixin
39
- from edsl.utilities.remove_edsl_version import remove_edsl_version
33
+ from edsl.results.ResultsFetchMixin import ResultsFetchMixin
34
+
35
+ from edsl.utilities.decorators import remove_edsl_version
36
+ from edsl.utilities.utilities import dict_hash
37
+
38
+
39
+ from edsl.Base import Base
40
40
 
41
41
 
42
42
  class Mixins(
43
43
  ResultsExportMixin,
44
+ ResultsDBMixin,
44
45
  ResultsFetchMixin,
45
46
  ResultsGGMixin,
47
+ ResultsToolsMixin,
46
48
  ):
47
49
  def long(self):
48
50
  return self.table().long()
@@ -89,7 +91,6 @@ class Results(UserList, Mixins, Base):
89
91
  "question_type",
90
92
  "comment",
91
93
  "generated_tokens",
92
- "cache_used",
93
94
  ]
94
95
 
95
96
  def __init__(
@@ -128,43 +129,22 @@ class Results(UserList, Mixins, Base):
128
129
  def _summary(self) -> dict:
129
130
  import reprlib
130
131
 
132
+ # import yaml
133
+
131
134
  d = {
132
- "observations": len(self),
133
- "agents": len(set(self.agents)),
134
- "models": len(set(self.models)),
135
- "scenarios": len(set(self.scenarios)),
136
- "questions": len(self.survey),
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),
137
142
  "Survey question names": reprlib.repr(self.survey.question_names),
143
+ "Object hash": hash(self),
138
144
  }
139
145
  return d
140
146
 
141
- def insert(self, item):
142
- item_order = getattr(item, "order", None)
143
- if item_order is not None:
144
- # Get list of orders, putting None at the end
145
- orders = [getattr(x, "order", None) for x in self]
146
- # Filter to just the non-None orders for bisect
147
- sorted_orders = [x for x in orders if x is not None]
148
- if sorted_orders:
149
- index = bisect_left(sorted_orders, item_order)
150
- # Account for any None values before this position
151
- index += orders[:index].count(None)
152
- else:
153
- # If no sorted items yet, insert before any unordered items
154
- index = 0
155
- self.data.insert(index, item)
156
- else:
157
- # No order - append to end
158
- self.data.append(item)
159
-
160
- def append(self, item):
161
- self.insert(item)
162
-
163
- def extend(self, other):
164
- for item in other:
165
- self.insert(item)
166
-
167
- def compute_job_cost(self, include_cached_responses_in_cost: bool = False) -> float:
147
+ def compute_job_cost(self, include_cached_responses_in_cost=False) -> float:
168
148
  """
169
149
  Computes the cost of a completed job in USD.
170
150
  """
@@ -278,6 +258,24 @@ class Results(UserList, Mixins, Base):
278
258
 
279
259
  raise TypeError("Invalid argument type")
280
260
 
261
+ def _update_results(self) -> None:
262
+ from edsl import Agent, Scenario
263
+ from edsl.language_models import LanguageModel
264
+ from edsl.results import Result
265
+
266
+ if self._job_uuid and len(self.data) < self._total_results:
267
+ results = [
268
+ Result(
269
+ agent=Agent.from_dict(json.loads(r.agent)),
270
+ scenario=Scenario.from_dict(json.loads(r.scenario)),
271
+ model=LanguageModel.from_dict(json.loads(r.model)),
272
+ iteration=1,
273
+ answer=json.loads(r.answer),
274
+ )
275
+ for r in CRUD.read_results(self._job_uuid)
276
+ ]
277
+ self.data = results
278
+
281
279
  def __add__(self, other: Results) -> Results:
282
280
  """Add two Results objects together.
283
281
  They must have the same survey and created columns.
@@ -305,10 +303,13 @@ class Results(UserList, Mixins, Base):
305
303
  )
306
304
 
307
305
  def __repr__(self) -> str:
308
- return f"Results(data = {self.data}, survey = {repr(self.survey)}, created_columns = {self.created_columns})"
306
+ import reprlib
307
+
308
+ return f"Results(data = {reprlib.repr(self.data)}, survey = {repr(self.survey)}, created_columns = {self.created_columns})"
309
309
 
310
310
  def table(
311
311
  self,
312
+ # selector_string: Optional[str] = "*.*",
312
313
  *fields,
313
314
  tablefmt: Optional[str] = None,
314
315
  pretty_labels: Optional[dict] = None,
@@ -344,14 +345,28 @@ class Results(UserList, Mixins, Base):
344
345
  print_parameters=print_parameters,
345
346
  )
346
347
  )
348
+ # return (
349
+ # self.select(f"{selector_string}")
350
+ # .to_scenario_list()
351
+ # .table(*fields, tablefmt=tablefmt)
352
+ # )
353
+
354
+ def _repr_html_(self) -> str:
355
+ d = self._summary()
356
+ from edsl import Scenario
357
+
358
+ footer = f"<a href={self.__documentation__}>(docs)</a>"
359
+
360
+ s = Scenario(d)
361
+ td = s.to_dataset().table(tablefmt="html")
362
+ return td._repr_html_() + footer
347
363
 
348
364
  def to_dict(
349
365
  self,
350
- sort: bool = False,
351
- add_edsl_version: bool = False,
352
- include_cache: bool = False,
353
- include_task_history: bool = False,
354
- include_cache_info: bool = True,
366
+ sort=False,
367
+ add_edsl_version=False,
368
+ include_cache=False,
369
+ include_task_history=False,
355
370
  ) -> dict[str, Any]:
356
371
  from edsl.data.Cache import Cache
357
372
 
@@ -362,11 +377,7 @@ class Results(UserList, Mixins, Base):
362
377
 
363
378
  d = {
364
379
  "data": [
365
- result.to_dict(
366
- add_edsl_version=add_edsl_version,
367
- include_cache_info=include_cache_info,
368
- )
369
- for result in data
380
+ result.to_dict(add_edsl_version=add_edsl_version) for result in data
370
381
  ],
371
382
  "survey": self.survey.to_dict(add_edsl_version=add_edsl_version),
372
383
  "created_columns": self.created_columns,
@@ -393,7 +404,7 @@ class Results(UserList, Mixins, Base):
393
404
 
394
405
  return d
395
406
 
396
- def compare(self, other_results: Results) -> dict:
407
+ def compare(self, other_results):
397
408
  """
398
409
  Compare two Results objects and return the differences.
399
410
  """
@@ -411,15 +422,11 @@ class Results(UserList, Mixins, Base):
411
422
  }
412
423
 
413
424
  @property
414
- def has_unfixed_exceptions(self) -> bool:
425
+ def has_unfixed_exceptions(self):
415
426
  return self.task_history.has_unfixed_exceptions
416
427
 
417
428
  def __hash__(self) -> int:
418
- from edsl.utilities.utilities import dict_hash
419
-
420
- return dict_hash(
421
- self.to_dict(sort=True, add_edsl_version=False, include_cache_info=False)
422
- )
429
+ return dict_hash(self.to_dict(sort=True, add_edsl_version=False))
423
430
 
424
431
  @property
425
432
  def hashes(self) -> set:
@@ -465,35 +472,32 @@ class Results(UserList, Mixins, Base):
465
472
  >>> r == r2
466
473
  True
467
474
  """
468
- from edsl.surveys.Survey import Survey
469
- from edsl.data.Cache import Cache
475
+ from edsl import Survey, Cache
470
476
  from edsl.results.Result import Result
471
477
  from edsl.jobs.tasks.TaskHistory import TaskHistory
472
- from edsl.agents.Agent import Agent
473
-
474
- survey = Survey.from_dict(data["survey"])
475
- results_data = [Result.from_dict(r) for r in data["data"]]
476
- created_columns = data.get("created_columns", None)
477
- cache = Cache.from_dict(data.get("cache")) if "cache" in data else Cache()
478
- task_history = (
479
- TaskHistory.from_dict(data.get("task_history"))
480
- if "task_history" in data
481
- else TaskHistory(interviews=[])
482
- )
483
- params = {
484
- "survey": survey,
485
- "data": results_data,
486
- "created_columns": created_columns,
487
- "cache": cache,
488
- "task_history": task_history,
489
- }
490
478
 
491
479
  try:
492
- results = cls(**params)
480
+ results = cls(
481
+ survey=Survey.from_dict(data["survey"]),
482
+ data=[Result.from_dict(r) for r in data["data"]],
483
+ created_columns=data.get("created_columns", None),
484
+ cache=(
485
+ Cache.from_dict(data.get("cache")) if "cache" in data else Cache()
486
+ ),
487
+ task_history=(
488
+ TaskHistory.from_dict(data.get("task_history"))
489
+ if "task_history" in data
490
+ else TaskHistory(interviews=[])
491
+ ),
492
+ )
493
493
  except Exception as e:
494
494
  raise ResultsDeserializationError(f"Error in Results.from_dict: {e}")
495
495
  return results
496
496
 
497
+ ######################
498
+ ## Convenience methods
499
+ ## & Report methods
500
+ ######################
497
501
  @property
498
502
  def _key_to_data_type(self) -> dict[str, str]:
499
503
  """
@@ -540,12 +544,10 @@ class Results(UserList, Mixins, Base):
540
544
 
541
545
  >>> r = Results.example()
542
546
  >>> r.columns
543
- ['agent.agent_index', ...]
547
+ ['agent.agent_instruction', ...]
544
548
  """
545
549
  column_names = [f"{v}.{k}" for k, v in self._key_to_data_type.items()]
546
- from edsl.utilities.PrettyList import PrettyList
547
-
548
- return PrettyList(sorted(column_names))
550
+ return sorted(column_names)
549
551
 
550
552
  @property
551
553
  def answer_keys(self) -> dict[str, str]:
@@ -565,7 +567,7 @@ class Results(UserList, Mixins, Base):
565
567
  answer_keys = self._data_type_to_keys["answer"]
566
568
  answer_keys = {k for k in answer_keys if "_comment" not in k}
567
569
  questions_text = [
568
- self.survey._get_question_by_name(k).question_text for k in answer_keys
570
+ self.survey.get_question(k).question_text for k in answer_keys
569
571
  ]
570
572
  short_question_text = [shorten_string(q, 80) for q in questions_text]
571
573
  initial_dict = dict(zip(answer_keys, short_question_text))
@@ -582,7 +584,7 @@ class Results(UserList, Mixins, Base):
582
584
  >>> r.agents
583
585
  AgentList([Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Sad'}), Agent(traits = {'status': 'Sad'})])
584
586
  """
585
- from edsl.agents.AgentList import AgentList
587
+ from edsl import AgentList
586
588
 
587
589
  return AgentList([r.agent for r in self.data])
588
590
 
@@ -596,13 +598,10 @@ class Results(UserList, Mixins, Base):
596
598
  >>> r.models[0]
597
599
  Model(model_name = ...)
598
600
  """
599
- from edsl.language_models.ModelList import ModelList
601
+ from edsl import ModelList
600
602
 
601
603
  return ModelList([r.model for r in self.data])
602
604
 
603
- def __eq__(self, other):
604
- return hash(self) == hash(other)
605
-
606
605
  @property
607
606
  def scenarios(self) -> ScenarioList:
608
607
  """Return a list of all of the scenarios in the Results.
@@ -611,9 +610,9 @@ class Results(UserList, Mixins, Base):
611
610
 
612
611
  >>> r = Results.example()
613
612
  >>> r.scenarios
614
- ScenarioList([Scenario({'period': 'morning', 'scenario_index': 0}), Scenario({'period': 'afternoon', 'scenario_index': 1}), Scenario({'period': 'morning', 'scenario_index': 0}), Scenario({'period': 'afternoon', 'scenario_index': 1})])
613
+ ScenarioList([Scenario({'period': 'morning'}), Scenario({'period': 'afternoon'}), Scenario({'period': 'morning'}), Scenario({'period': 'afternoon'})])
615
614
  """
616
- from edsl.scenarios.ScenarioList import ScenarioList
615
+ from edsl import ScenarioList
617
616
 
618
617
  return ScenarioList([r.scenario for r in self.data])
619
618
 
@@ -625,7 +624,7 @@ class Results(UserList, Mixins, Base):
625
624
 
626
625
  >>> r = Results.example()
627
626
  >>> r.agent_keys
628
- ['agent_index', 'agent_instruction', 'agent_name', 'status']
627
+ ['agent_instruction', 'agent_name', 'status']
629
628
  """
630
629
  return sorted(self._data_type_to_keys["agent"])
631
630
 
@@ -635,7 +634,7 @@ class Results(UserList, Mixins, Base):
635
634
 
636
635
  >>> r = Results.example()
637
636
  >>> r.model_keys
638
- ['frequency_penalty', 'logprobs', 'max_tokens', 'model', 'model_index', 'presence_penalty', 'temperature', 'top_logprobs', 'top_p']
637
+ ['frequency_penalty', 'logprobs', 'max_tokens', 'model', 'presence_penalty', 'temperature', 'top_logprobs', 'top_p']
639
638
  """
640
639
  return sorted(self._data_type_to_keys["model"])
641
640
 
@@ -645,7 +644,7 @@ class Results(UserList, Mixins, Base):
645
644
 
646
645
  >>> r = Results.example()
647
646
  >>> r.scenario_keys
648
- ['period', 'scenario_index']
647
+ ['period']
649
648
  """
650
649
  return sorted(self._data_type_to_keys["scenario"])
651
650
 
@@ -671,7 +670,7 @@ class Results(UserList, Mixins, Base):
671
670
 
672
671
  >>> r = Results.example()
673
672
  >>> r.all_keys
674
- ['agent_index', ...]
673
+ ['agent_instruction', 'agent_name', 'frequency_penalty', 'how_feeling', 'how_feeling_yesterday', 'logprobs', 'max_tokens', 'model', 'period', 'presence_penalty', 'status', 'temperature', 'top_logprobs', 'top_p']
675
674
  """
676
675
  answer_keys = set(self.answer_keys)
677
676
  all_keys = (
@@ -692,19 +691,13 @@ class Results(UserList, Mixins, Base):
692
691
  """
693
692
  return self.data[0]
694
693
 
695
- def answer_truncate(
696
- self, column: str, top_n: int = 5, new_var_name: str = None
697
- ) -> Results:
694
+ def answer_truncate(self, column: str, top_n=5, new_var_name=None) -> Results:
698
695
  """Create a new variable that truncates the answers to the top_n.
699
696
 
700
697
  :param column: The column to truncate.
701
698
  :param top_n: The number of top answers to keep.
702
699
  :param new_var_name: The name of the new variable. If None, it is the original name + '_truncated'.
703
700
 
704
- Example:
705
- >>> r = Results.example()
706
- >>> r.answer_truncate('how_feeling', top_n = 2).select('how_feeling', 'how_feeling_truncated')
707
- Dataset([{'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}, {'answer.how_feeling_truncated': ['Other', 'Other', 'Other', 'Other']}])
708
701
 
709
702
 
710
703
  """
@@ -784,7 +777,7 @@ class Results(UserList, Mixins, Base):
784
777
  @staticmethod
785
778
  def _create_evaluator(
786
779
  result: Result, functions_dict: Optional[dict] = None
787
- ) -> "EvalWithCompoundTypes":
780
+ ) -> EvalWithCompoundTypes:
788
781
  """Create an evaluator for the expression.
789
782
 
790
783
  >>> from unittest.mock import Mock
@@ -807,8 +800,6 @@ class Results(UserList, Mixins, Base):
807
800
  ...
808
801
  simpleeval.NameNotDefined: 'how_feeling' is not defined for expression 'how_feeling== 'OK''
809
802
  """
810
- from simpleeval import EvalWithCompoundTypes
811
-
812
803
  if functions_dict is None:
813
804
  functions_dict = {}
814
805
  evaluator = EvalWithCompoundTypes(
@@ -867,26 +858,6 @@ class Results(UserList, Mixins, Base):
867
858
  created_columns=self.created_columns + [var_name],
868
859
  )
869
860
 
870
- def add_column(self, column_name: str, values: list) -> Results:
871
- """Adds columns to Results
872
-
873
- >>> r = Results.example()
874
- >>> r.add_column('a', [1,2,3, 4]).select('a')
875
- Dataset([{'answer.a': [1, 2, 3, 4]}])
876
- """
877
-
878
- assert len(values) == len(
879
- self.data
880
- ), "The number of values must match the number of results."
881
- new_results = self.data.copy()
882
- for i, result in enumerate(new_results):
883
- result["answer"][column_name] = values[i]
884
- return Results(
885
- survey=self.survey,
886
- data=new_results,
887
- created_columns=self.created_columns + [column_name],
888
- )
889
-
890
861
  def rename(self, old_name: str, new_name: str) -> Results:
891
862
  """Rename an answer column in a Results object.
892
863
 
@@ -925,7 +896,7 @@ class Results(UserList, Mixins, Base):
925
896
  n: Optional[int] = None,
926
897
  frac: Optional[float] = None,
927
898
  with_replacement: bool = True,
928
- seed: Optional[str] = None,
899
+ seed: Optional[str] = "edsl",
929
900
  ) -> Results:
930
901
  """Sample the results.
931
902
 
@@ -940,7 +911,7 @@ class Results(UserList, Mixins, Base):
940
911
  >>> len(r.sample(2))
941
912
  2
942
913
  """
943
- if seed:
914
+ if seed != "edsl":
944
915
  random.seed(seed)
945
916
 
946
917
  if n is None and frac is None:
@@ -978,7 +949,7 @@ class Results(UserList, Mixins, Base):
978
949
  Dataset([{'answer.how_feeling_yesterday': ['Great', 'Good', 'OK', 'Terrible']}])
979
950
  """
980
951
 
981
- from edsl.results.results_selector import Selector
952
+ from edsl.results.Selector import Selector
982
953
 
983
954
  if len(self) == 0:
984
955
  raise Exception("No data to select from---the Results object is empty.")
@@ -993,7 +964,6 @@ class Results(UserList, Mixins, Base):
993
964
  return selector.select(*columns)
994
965
 
995
966
  def sort_by(self, *columns: str, reverse: bool = False) -> Results:
996
- """Sort the results by one or more columns."""
997
967
  import warnings
998
968
 
999
969
  warnings.warn(
@@ -1002,7 +972,6 @@ class Results(UserList, Mixins, Base):
1002
972
  return self.order_by(*columns, reverse=reverse)
1003
973
 
1004
974
  def _parse_column(self, column: str) -> tuple[str, str]:
1005
- """Parse a column name into a data type and key."""
1006
975
  if "." in column:
1007
976
  return column.split(".")
1008
977
  return self._key_to_data_type[column], column
@@ -1018,12 +987,20 @@ class Results(UserList, Mixins, Base):
1018
987
  Example:
1019
988
 
1020
989
  >>> r = Results.example()
1021
- >>> r.sort_by('how_feeling', reverse=False).select('how_feeling')
1022
- Dataset([{'answer.how_feeling': ['Great', 'OK', 'OK', 'Terrible']}])
1023
-
1024
- >>> r.sort_by('how_feeling', reverse=True).select('how_feeling')
1025
- Dataset([{'answer.how_feeling': ['Terrible', 'OK', 'OK', 'Great']}])
1026
-
990
+ >>> r.sort_by('how_feeling', reverse=False).select('how_feeling').print()
991
+ answer.how_feeling
992
+ --------------------
993
+ Great
994
+ OK
995
+ OK
996
+ Terrible
997
+ >>> r.sort_by('how_feeling', reverse=True).select('how_feeling').print()
998
+ answer.how_feeling
999
+ --------------------
1000
+ Terrible
1001
+ OK
1002
+ OK
1003
+ Great
1027
1004
  """
1028
1005
 
1029
1006
  def to_numeric_if_possible(v):
@@ -1055,19 +1032,24 @@ class Results(UserList, Mixins, Base):
1055
1032
  Example usage: Create an example `Results` instance and apply filters to it:
1056
1033
 
1057
1034
  >>> r = Results.example()
1058
- >>> r.filter("how_feeling == 'Great'").select('how_feeling')
1059
- Dataset([{'answer.how_feeling': ['Great']}])
1035
+ >>> r.filter("how_feeling == 'Great'").select('how_feeling').print()
1036
+ answer.how_feeling
1037
+ --------------------
1038
+ Great
1060
1039
 
1061
1040
  Example usage: Using an OR operator in the filter expression.
1062
1041
 
1063
- >>> r = Results.example().filter("how_feeling = 'Great'").select('how_feeling')
1042
+ >>> r = Results.example().filter("how_feeling = 'Great'").select('how_feeling').print()
1064
1043
  Traceback (most recent call last):
1065
1044
  ...
1066
1045
  edsl.exceptions.results.ResultsFilterError: You must use '==' instead of '=' in the filter expression.
1067
1046
  ...
1068
1047
 
1069
- >>> r.filter("how_feeling == 'Great' or how_feeling == 'Terrible'").select('how_feeling')
1070
- Dataset([{'answer.how_feeling': ['Great', 'Terrible']}])
1048
+ >>> r.filter("how_feeling == 'Great' or how_feeling == 'Terrible'").select('how_feeling').print()
1049
+ answer.how_feeling
1050
+ --------------------
1051
+ Great
1052
+ Terrible
1071
1053
  """
1072
1054
 
1073
1055
  def has_single_equals(string):