edsl 0.1.47__py3-none-any.whl → 0.1.48__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 (314) hide show
  1. edsl/__init__.py +44 -39
  2. edsl/__version__.py +1 -1
  3. edsl/agents/__init__.py +4 -2
  4. edsl/agents/{Agent.py → agent.py} +442 -152
  5. edsl/agents/{AgentList.py → agent_list.py} +220 -162
  6. edsl/agents/descriptors.py +46 -7
  7. edsl/{exceptions/agents.py → agents/exceptions.py} +3 -12
  8. edsl/base/__init__.py +75 -0
  9. edsl/base/base_class.py +1303 -0
  10. edsl/base/data_transfer_models.py +114 -0
  11. edsl/base/enums.py +215 -0
  12. edsl/base.py +8 -0
  13. edsl/buckets/__init__.py +25 -0
  14. edsl/buckets/bucket_collection.py +324 -0
  15. edsl/buckets/model_buckets.py +206 -0
  16. edsl/buckets/token_bucket.py +502 -0
  17. edsl/{jobs/buckets/TokenBucketAPI.py → buckets/token_bucket_api.py} +1 -1
  18. edsl/buckets/token_bucket_client.py +509 -0
  19. edsl/caching/__init__.py +20 -0
  20. edsl/caching/cache.py +814 -0
  21. edsl/caching/cache_entry.py +427 -0
  22. edsl/{data/CacheHandler.py → caching/cache_handler.py} +14 -15
  23. edsl/caching/exceptions.py +24 -0
  24. edsl/caching/orm.py +30 -0
  25. edsl/{data/RemoteCacheSync.py → caching/remote_cache_sync.py} +3 -3
  26. edsl/caching/sql_dict.py +441 -0
  27. edsl/config/__init__.py +8 -0
  28. edsl/config/config_class.py +177 -0
  29. edsl/config.py +4 -176
  30. edsl/conversation/Conversation.py +7 -7
  31. edsl/conversation/car_buying.py +4 -4
  32. edsl/conversation/chips.py +6 -6
  33. edsl/coop/__init__.py +25 -2
  34. edsl/coop/coop.py +303 -67
  35. edsl/coop/{ExpectedParrotKeyHandler.py → ep_key_handling.py} +86 -10
  36. edsl/coop/exceptions.py +62 -0
  37. edsl/coop/price_fetcher.py +126 -0
  38. edsl/coop/utils.py +89 -24
  39. edsl/data_transfer_models.py +5 -72
  40. edsl/dataset/__init__.py +10 -0
  41. edsl/{results/Dataset.py → dataset/dataset.py} +116 -36
  42. edsl/{results/DatasetExportMixin.py → dataset/dataset_operations_mixin.py} +606 -122
  43. edsl/{results/DatasetTree.py → dataset/dataset_tree.py} +156 -75
  44. edsl/{results/TableDisplay.py → dataset/display/table_display.py} +18 -7
  45. edsl/{results → dataset/display}/table_renderers.py +58 -2
  46. edsl/{results → dataset}/file_exports.py +4 -5
  47. edsl/{results → dataset}/smart_objects.py +2 -2
  48. edsl/enums.py +5 -205
  49. edsl/inference_services/__init__.py +5 -0
  50. edsl/inference_services/{AvailableModelCacheHandler.py → available_model_cache_handler.py} +2 -3
  51. edsl/inference_services/{AvailableModelFetcher.py → available_model_fetcher.py} +8 -14
  52. edsl/inference_services/data_structures.py +3 -2
  53. edsl/{exceptions/inference_services.py → inference_services/exceptions.py} +1 -1
  54. edsl/inference_services/{InferenceServiceABC.py → inference_service_abc.py} +1 -1
  55. edsl/inference_services/{InferenceServicesCollection.py → inference_services_collection.py} +8 -7
  56. edsl/inference_services/registry.py +4 -41
  57. edsl/inference_services/{ServiceAvailability.py → service_availability.py} +5 -25
  58. edsl/inference_services/services/__init__.py +31 -0
  59. edsl/inference_services/{AnthropicService.py → services/anthropic_service.py} +3 -3
  60. edsl/inference_services/{AwsBedrock.py → services/aws_bedrock.py} +2 -2
  61. edsl/inference_services/{AzureAI.py → services/azure_ai.py} +2 -2
  62. edsl/inference_services/{DeepInfraService.py → services/deep_infra_service.py} +1 -3
  63. edsl/inference_services/{DeepSeekService.py → services/deep_seek_service.py} +2 -4
  64. edsl/inference_services/{GoogleService.py → services/google_service.py} +5 -4
  65. edsl/inference_services/{GroqService.py → services/groq_service.py} +1 -1
  66. edsl/inference_services/{MistralAIService.py → services/mistral_ai_service.py} +3 -3
  67. edsl/inference_services/{OllamaService.py → services/ollama_service.py} +1 -7
  68. edsl/inference_services/{OpenAIService.py → services/open_ai_service.py} +5 -6
  69. edsl/inference_services/{PerplexityService.py → services/perplexity_service.py} +3 -7
  70. edsl/inference_services/{TestService.py → services/test_service.py} +7 -6
  71. edsl/inference_services/{TogetherAIService.py → services/together_ai_service.py} +2 -6
  72. edsl/inference_services/{XAIService.py → services/xai_service.py} +1 -1
  73. edsl/inference_services/write_available.py +1 -2
  74. edsl/instructions/__init__.py +6 -0
  75. edsl/{surveys/instructions/Instruction.py → instructions/instruction.py} +11 -6
  76. edsl/{surveys/instructions/InstructionCollection.py → instructions/instruction_collection.py} +10 -5
  77. edsl/{surveys/InstructionHandler.py → instructions/instruction_handler.py} +3 -3
  78. edsl/{jobs/interviews → interviews}/ReportErrors.py +2 -2
  79. edsl/interviews/__init__.py +4 -0
  80. edsl/{jobs/AnswerQuestionFunctionConstructor.py → interviews/answering_function.py} +45 -18
  81. edsl/{jobs/interviews/InterviewExceptionEntry.py → interviews/exception_tracking.py} +107 -22
  82. edsl/interviews/interview.py +638 -0
  83. edsl/{jobs/interviews/InterviewStatusDictionary.py → interviews/interview_status_dictionary.py} +21 -12
  84. edsl/{jobs/interviews/InterviewStatusLog.py → interviews/interview_status_log.py} +16 -7
  85. edsl/{jobs/InterviewTaskManager.py → interviews/interview_task_manager.py} +12 -7
  86. edsl/{jobs/RequestTokenEstimator.py → interviews/request_token_estimator.py} +8 -3
  87. edsl/{jobs/interviews/InterviewStatistic.py → interviews/statistics.py} +36 -10
  88. edsl/invigilators/__init__.py +38 -0
  89. edsl/invigilators/invigilator_base.py +477 -0
  90. edsl/{agents/Invigilator.py → invigilators/invigilators.py} +263 -10
  91. edsl/invigilators/prompt_constructor.py +476 -0
  92. edsl/{agents → invigilators}/prompt_helpers.py +2 -1
  93. edsl/{agents/QuestionInstructionPromptBuilder.py → invigilators/question_instructions_prompt_builder.py} +18 -13
  94. edsl/{agents → invigilators}/question_option_processor.py +96 -21
  95. edsl/{agents/QuestionTemplateReplacementsBuilder.py → invigilators/question_template_replacements_builder.py} +64 -12
  96. edsl/jobs/__init__.py +7 -1
  97. edsl/jobs/async_interview_runner.py +99 -35
  98. edsl/jobs/check_survey_scenario_compatibility.py +7 -5
  99. edsl/jobs/data_structures.py +153 -22
  100. edsl/{exceptions/jobs.py → jobs/exceptions.py} +2 -1
  101. edsl/jobs/{FetchInvigilator.py → fetch_invigilator.py} +4 -4
  102. edsl/jobs/{loggers/HTMLTableJobLogger.py → html_table_job_logger.py} +6 -2
  103. edsl/jobs/{Jobs.py → jobs.py} +313 -167
  104. edsl/jobs/{JobsChecks.py → jobs_checks.py} +15 -7
  105. edsl/jobs/{JobsComponentConstructor.py → jobs_component_constructor.py} +19 -17
  106. edsl/jobs/{InterviewsConstructor.py → jobs_interview_constructor.py} +10 -5
  107. edsl/jobs/jobs_pricing_estimation.py +347 -0
  108. edsl/jobs/{JobsRemoteInferenceLogger.py → jobs_remote_inference_logger.py} +4 -3
  109. edsl/jobs/jobs_runner_asyncio.py +282 -0
  110. edsl/jobs/{JobsRemoteInferenceHandler.py → remote_inference.py} +19 -22
  111. edsl/jobs/results_exceptions_handler.py +2 -2
  112. edsl/key_management/__init__.py +28 -0
  113. edsl/key_management/key_lookup.py +161 -0
  114. edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py} +118 -47
  115. edsl/key_management/key_lookup_collection.py +82 -0
  116. edsl/key_management/models.py +218 -0
  117. edsl/language_models/__init__.py +7 -2
  118. edsl/language_models/{ComputeCost.py → compute_cost.py} +18 -3
  119. edsl/{exceptions/language_models.py → language_models/exceptions.py} +2 -1
  120. edsl/language_models/language_model.py +1080 -0
  121. edsl/language_models/model.py +10 -25
  122. edsl/language_models/{ModelList.py → model_list.py} +9 -14
  123. edsl/language_models/{RawResponseHandler.py → raw_response_handler.py} +1 -1
  124. edsl/language_models/{RegisterLanguageModelsMeta.py → registry.py} +1 -1
  125. edsl/language_models/repair.py +4 -4
  126. edsl/language_models/utilities.py +4 -4
  127. edsl/notebooks/__init__.py +3 -1
  128. edsl/notebooks/{Notebook.py → notebook.py} +7 -8
  129. edsl/prompts/__init__.py +1 -1
  130. edsl/{exceptions/prompts.py → prompts/exceptions.py} +3 -1
  131. edsl/prompts/{Prompt.py → prompt.py} +101 -95
  132. edsl/questions/HTMLQuestion.py +1 -1
  133. edsl/questions/__init__.py +154 -25
  134. edsl/questions/answer_validator_mixin.py +1 -1
  135. edsl/questions/compose_questions.py +4 -3
  136. edsl/questions/derived/question_likert_five.py +166 -0
  137. edsl/questions/derived/{QuestionLinearScale.py → question_linear_scale.py} +4 -4
  138. edsl/questions/derived/{QuestionTopK.py → question_top_k.py} +4 -4
  139. edsl/questions/derived/{QuestionYesNo.py → question_yes_no.py} +4 -5
  140. edsl/questions/descriptors.py +24 -30
  141. edsl/questions/loop_processor.py +65 -19
  142. edsl/questions/question_base.py +881 -0
  143. edsl/questions/question_base_gen_mixin.py +15 -16
  144. edsl/questions/{QuestionBasePromptsMixin.py → question_base_prompts_mixin.py} +2 -2
  145. edsl/questions/{QuestionBudget.py → question_budget.py} +3 -4
  146. edsl/questions/{QuestionCheckBox.py → question_check_box.py} +16 -16
  147. edsl/questions/{QuestionDict.py → question_dict.py} +39 -5
  148. edsl/questions/{QuestionExtract.py → question_extract.py} +9 -9
  149. edsl/questions/question_free_text.py +282 -0
  150. edsl/questions/{QuestionFunctional.py → question_functional.py} +6 -5
  151. edsl/questions/{QuestionList.py → question_list.py} +6 -7
  152. edsl/questions/{QuestionMatrix.py → question_matrix.py} +6 -5
  153. edsl/questions/{QuestionMultipleChoice.py → question_multiple_choice.py} +126 -21
  154. edsl/questions/{QuestionNumerical.py → question_numerical.py} +5 -5
  155. edsl/questions/{QuestionRank.py → question_rank.py} +6 -6
  156. edsl/questions/question_registry.py +4 -9
  157. edsl/questions/register_questions_meta.py +8 -4
  158. edsl/questions/response_validator_abc.py +17 -16
  159. edsl/results/__init__.py +4 -1
  160. edsl/{exceptions/results.py → results/exceptions.py} +1 -1
  161. edsl/results/report.py +197 -0
  162. edsl/results/{Result.py → result.py} +131 -45
  163. edsl/results/{Results.py → results.py} +365 -220
  164. edsl/results/results_selector.py +344 -25
  165. edsl/scenarios/__init__.py +30 -3
  166. edsl/scenarios/{ConstructDownloadLink.py → construct_download_link.py} +7 -0
  167. edsl/scenarios/directory_scanner.py +156 -13
  168. edsl/scenarios/document_chunker.py +186 -0
  169. edsl/scenarios/exceptions.py +101 -0
  170. edsl/scenarios/file_methods.py +2 -3
  171. edsl/scenarios/{FileStore.py → file_store.py} +275 -189
  172. edsl/scenarios/handlers/__init__.py +14 -14
  173. edsl/scenarios/handlers/{csv.py → csv_file_store.py} +1 -2
  174. edsl/scenarios/handlers/{docx.py → docx_file_store.py} +8 -7
  175. edsl/scenarios/handlers/{html.py → html_file_store.py} +1 -2
  176. edsl/scenarios/handlers/{jpeg.py → jpeg_file_store.py} +1 -1
  177. edsl/scenarios/handlers/{json.py → json_file_store.py} +1 -1
  178. edsl/scenarios/handlers/latex_file_store.py +5 -0
  179. edsl/scenarios/handlers/{md.py → md_file_store.py} +1 -1
  180. edsl/scenarios/handlers/{pdf.py → pdf_file_store.py} +2 -2
  181. edsl/scenarios/handlers/{png.py → png_file_store.py} +1 -1
  182. edsl/scenarios/handlers/{pptx.py → pptx_file_store.py} +8 -7
  183. edsl/scenarios/handlers/{py.py → py_file_store.py} +1 -3
  184. edsl/scenarios/handlers/{sql.py → sql_file_store.py} +2 -1
  185. edsl/scenarios/handlers/{sqlite.py → sqlite_file_store.py} +2 -3
  186. edsl/scenarios/handlers/{txt.py → txt_file_store.py} +1 -1
  187. edsl/scenarios/scenario.py +928 -0
  188. edsl/scenarios/scenario_join.py +18 -5
  189. edsl/scenarios/{ScenarioList.py → scenario_list.py} +294 -106
  190. edsl/scenarios/{ScenarioListPdfMixin.py → scenario_list_pdf_tools.py} +16 -15
  191. edsl/scenarios/scenario_selector.py +5 -1
  192. edsl/study/ObjectEntry.py +2 -2
  193. edsl/study/SnapShot.py +5 -5
  194. edsl/study/Study.py +18 -19
  195. edsl/study/__init__.py +6 -4
  196. edsl/surveys/__init__.py +7 -4
  197. edsl/surveys/dag/__init__.py +2 -0
  198. edsl/surveys/{ConstructDAG.py → dag/construct_dag.py} +3 -3
  199. edsl/surveys/{DAG.py → dag/dag.py} +13 -10
  200. edsl/surveys/descriptors.py +1 -1
  201. edsl/surveys/{EditSurvey.py → edit_survey.py} +9 -9
  202. edsl/{exceptions/surveys.py → surveys/exceptions.py} +1 -2
  203. edsl/surveys/memory/__init__.py +3 -0
  204. edsl/surveys/{MemoryPlan.py → memory/memory_plan.py} +10 -9
  205. edsl/surveys/rules/__init__.py +3 -0
  206. edsl/surveys/{Rule.py → rules/rule.py} +103 -43
  207. edsl/surveys/{RuleCollection.py → rules/rule_collection.py} +21 -30
  208. edsl/surveys/{RuleManager.py → rules/rule_manager.py} +19 -13
  209. edsl/surveys/survey.py +1743 -0
  210. edsl/surveys/{SurveyExportMixin.py → survey_export.py} +22 -27
  211. edsl/surveys/{SurveyFlowVisualization.py → survey_flow_visualization.py} +11 -2
  212. edsl/surveys/{Simulator.py → survey_simulator.py} +10 -3
  213. edsl/tasks/__init__.py +32 -0
  214. edsl/{jobs/tasks/QuestionTaskCreator.py → tasks/question_task_creator.py} +115 -57
  215. edsl/tasks/task_creators.py +135 -0
  216. edsl/{jobs/tasks/TaskHistory.py → tasks/task_history.py} +86 -47
  217. edsl/{jobs/tasks → tasks}/task_status_enum.py +91 -7
  218. edsl/tasks/task_status_log.py +85 -0
  219. edsl/tokens/__init__.py +2 -0
  220. edsl/tokens/interview_token_usage.py +53 -0
  221. edsl/utilities/PrettyList.py +1 -1
  222. edsl/utilities/SystemInfo.py +25 -22
  223. edsl/utilities/__init__.py +29 -21
  224. edsl/utilities/gcp_bucket/__init__.py +2 -0
  225. edsl/utilities/gcp_bucket/cloud_storage.py +99 -96
  226. edsl/utilities/interface.py +44 -536
  227. edsl/{results/MarkdownToPDF.py → utilities/markdown_to_pdf.py} +13 -5
  228. edsl/utilities/repair_functions.py +1 -1
  229. {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/METADATA +1 -1
  230. edsl-0.1.48.dist-info/RECORD +347 -0
  231. edsl/Base.py +0 -493
  232. edsl/BaseDiff.py +0 -260
  233. edsl/agents/InvigilatorBase.py +0 -260
  234. edsl/agents/PromptConstructor.py +0 -318
  235. edsl/coop/PriceFetcher.py +0 -54
  236. edsl/data/Cache.py +0 -582
  237. edsl/data/CacheEntry.py +0 -238
  238. edsl/data/SQLiteDict.py +0 -292
  239. edsl/data/__init__.py +0 -5
  240. edsl/data/orm.py +0 -10
  241. edsl/exceptions/cache.py +0 -5
  242. edsl/exceptions/coop.py +0 -14
  243. edsl/exceptions/data.py +0 -14
  244. edsl/exceptions/scenarios.py +0 -29
  245. edsl/jobs/Answers.py +0 -43
  246. edsl/jobs/JobsPrompts.py +0 -354
  247. edsl/jobs/buckets/BucketCollection.py +0 -134
  248. edsl/jobs/buckets/ModelBuckets.py +0 -65
  249. edsl/jobs/buckets/TokenBucket.py +0 -283
  250. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  251. edsl/jobs/interviews/Interview.py +0 -395
  252. edsl/jobs/interviews/InterviewExceptionCollection.py +0 -99
  253. edsl/jobs/interviews/InterviewStatisticsCollection.py +0 -25
  254. edsl/jobs/runners/JobsRunnerAsyncio.py +0 -163
  255. edsl/jobs/runners/JobsRunnerStatusData.py +0 -0
  256. edsl/jobs/tasks/TaskCreators.py +0 -64
  257. edsl/jobs/tasks/TaskStatusLog.py +0 -23
  258. edsl/jobs/tokens/InterviewTokenUsage.py +0 -27
  259. edsl/language_models/LanguageModel.py +0 -635
  260. edsl/language_models/ServiceDataSources.py +0 -0
  261. edsl/language_models/key_management/KeyLookup.py +0 -63
  262. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  263. edsl/language_models/key_management/models.py +0 -137
  264. edsl/questions/QuestionBase.py +0 -544
  265. edsl/questions/QuestionFreeText.py +0 -130
  266. edsl/questions/derived/QuestionLikertFive.py +0 -76
  267. edsl/results/ResultsExportMixin.py +0 -45
  268. edsl/results/TextEditor.py +0 -50
  269. edsl/results/results_fetch_mixin.py +0 -33
  270. edsl/results/results_tools_mixin.py +0 -98
  271. edsl/scenarios/DocumentChunker.py +0 -104
  272. edsl/scenarios/Scenario.py +0 -548
  273. edsl/scenarios/ScenarioHtmlMixin.py +0 -65
  274. edsl/scenarios/ScenarioListExportMixin.py +0 -45
  275. edsl/scenarios/handlers/latex.py +0 -5
  276. edsl/shared.py +0 -1
  277. edsl/surveys/Survey.py +0 -1301
  278. edsl/surveys/SurveyQualtricsImport.py +0 -284
  279. edsl/surveys/SurveyToApp.py +0 -141
  280. edsl/surveys/instructions/__init__.py +0 -0
  281. edsl/tools/__init__.py +0 -1
  282. edsl/tools/clusters.py +0 -192
  283. edsl/tools/embeddings.py +0 -27
  284. edsl/tools/embeddings_plotting.py +0 -118
  285. edsl/tools/plotting.py +0 -112
  286. edsl/tools/summarize.py +0 -18
  287. edsl/utilities/data/Registry.py +0 -6
  288. edsl/utilities/data/__init__.py +0 -1
  289. edsl/utilities/data/scooter_results.json +0 -1
  290. edsl-0.1.47.dist-info/RECORD +0 -354
  291. /edsl/coop/{CoopFunctionsMixin.py → coop_functions.py} +0 -0
  292. /edsl/{results → dataset/display}/CSSParameterizer.py +0 -0
  293. /edsl/{language_models/key_management → dataset/display}/__init__.py +0 -0
  294. /edsl/{results → dataset/display}/table_data_class.py +0 -0
  295. /edsl/{results → dataset/display}/table_display.css +0 -0
  296. /edsl/{results/ResultsGGMixin.py → dataset/r/ggplot.py} +0 -0
  297. /edsl/{results → dataset}/tree_explore.py +0 -0
  298. /edsl/{surveys/instructions/ChangeInstruction.py → instructions/change_instruction.py} +0 -0
  299. /edsl/{jobs/interviews → interviews}/interview_status_enum.py +0 -0
  300. /edsl/jobs/{runners/JobsRunnerStatus.py → jobs_runner_status.py} +0 -0
  301. /edsl/language_models/{PriceManager.py → price_manager.py} +0 -0
  302. /edsl/language_models/{fake_openai_call.py → unused/fake_openai_call.py} +0 -0
  303. /edsl/language_models/{fake_openai_service.py → unused/fake_openai_service.py} +0 -0
  304. /edsl/notebooks/{NotebookToLaTeX.py → notebook_to_latex.py} +0 -0
  305. /edsl/{exceptions/questions.py → questions/exceptions.py} +0 -0
  306. /edsl/questions/{SimpleAskMixin.py → simple_ask_mixin.py} +0 -0
  307. /edsl/surveys/{Memory.py → memory/memory.py} +0 -0
  308. /edsl/surveys/{MemoryManagement.py → memory/memory_management.py} +0 -0
  309. /edsl/surveys/{SurveyCSS.py → survey_css.py} +0 -0
  310. /edsl/{jobs/tokens/TokenUsage.py → tokens/token_usage.py} +0 -0
  311. /edsl/{results/MarkdownToDocx.py → utilities/markdown_to_docx.py} +0 -0
  312. /edsl/{TemplateLoader.py → utilities/template_loader.py} +0 -0
  313. {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/LICENSE +0 -0
  314. {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/WHEEL +0 -0
@@ -1,18 +1,67 @@
1
1
  """
2
- The Results object is the result of running a survey.
3
- It is not typically instantiated directly, but is returned by the run method of a `Job` object.
2
+ The Results module provides tools for working with collections of Result objects.
3
+
4
+ The Results class is the primary container for analyzing and manipulating data obtained
5
+ from running surveys with language models. It implements a powerful data analysis interface
6
+ with methods for filtering, selecting, mutating, and visualizing your results, similar to
7
+ data manipulation libraries like dplyr or pandas.
8
+
9
+ Key components:
10
+
11
+ 1. Results - A collection of Result objects with methods for data analysis and manipulation
12
+ 2. Report - A flexible reporting system for generating formatted output from Results
13
+ 3. Selectors - Tools for efficiently extracting specific data from Results
14
+
15
+ The Results class is not typically instantiated directly; instead, it's returned by the
16
+ run() method of a Job object. Once you have a Results object, you can use its methods
17
+ to analyze and extract insights from your survey data.
18
+
19
+ Example workflow:
20
+ ```python
21
+ # Run a job and get results
22
+ results = job.run()
23
+
24
+ # Filter to a subset of results
25
+ filtered = results.filter("how_feeling == 'Great'")
26
+
27
+ # Select specific columns for analysis
28
+ data = filtered.select("how_feeling", "agent.status")
29
+
30
+ # Create a new derived column
31
+ with_sentiment = results.mutate("sentiment = 1 if how_feeling == 'Great' else 0")
32
+
33
+ # Generate a report
34
+ report = Report(results, fields=["answer.how_feeling", "answer.sentiment"])
35
+ print(report.generate())
36
+ ```
4
37
  """
5
38
 
6
39
  from __future__ import annotations
7
40
  import json
8
41
  import random
42
+ import warnings
9
43
  from collections import UserList, defaultdict
10
44
  from typing import Optional, Callable, Any, Type, Union, List, TYPE_CHECKING
11
-
12
45
  from bisect import bisect_left
13
46
 
14
- from edsl.Base import Base
15
- from edsl.exceptions.results import (
47
+ from ..base import Base
48
+
49
+ if TYPE_CHECKING:
50
+ from ..surveys import Survey
51
+ from ..data import Cache
52
+ from ..agents import AgentList
53
+ from ..language_models import Model
54
+ from ..scenarios import ScenarioList
55
+ from ..results import Result
56
+ from ..tasks import TaskHistory
57
+ from ..language_models import ModelList
58
+ from simpleeval import EvalWithCompoundTypes
59
+ from ..dataset import Dataset
60
+
61
+ from ..utilities import remove_edsl_version, dict_hash
62
+ from ..dataset import ResultsOperationsMixin
63
+
64
+ from .exceptions import (
16
65
  ResultsError,
17
66
  ResultsBadMutationstringError,
18
67
  ResultsColumnNotFoundError,
@@ -22,22 +71,6 @@ from edsl.exceptions.results import (
22
71
  ResultsDeserializationError,
23
72
  )
24
73
 
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
- from edsl.results.ResultsExportMixin import ResultsExportMixin
37
- from edsl.results.ResultsGGMixin import GGPlotMethod
38
- from edsl.results.results_fetch_mixin import ResultsFetchMixin
39
- from edsl.utilities.remove_edsl_version import remove_edsl_version
40
-
41
74
  def ensure_fetched(method):
42
75
  """A decorator that checks if remote data is loaded, and if not, attempts to fetch it."""
43
76
  def wrapper(self, *args, **kwargs):
@@ -80,7 +113,7 @@ def ensure_ready(method):
80
113
 
81
114
  class NotReadyObject:
82
115
  """A placeholder object that prints a message when any attribute is accessed."""
83
- def __init__(self, name: str, job_info: RemoteJobInfo):
116
+ def __init__(self, name: str, job_info: 'RemoteJobInfo'):
84
117
  self.name = name
85
118
  self.job_info = job_info
86
119
  #print(f"Not ready to call {name}")
@@ -97,39 +130,40 @@ class NotReadyObject:
97
130
  def __call__(self, *args, **kwargs):
98
131
  return self
99
132
 
100
- class Mixins(
101
- ResultsExportMixin,
102
- ResultsFetchMixin,
103
- # ResultsGGMixin,
104
- ):
105
- def long(self):
106
- return self.table().long()
107
-
108
- def print_long(self, max_rows: int = None) -> None:
109
- """Print the results in long format.
110
-
111
- >>> from edsl.results import Results
112
- >>> r = Results.example()
113
- >>> r.select('how_feeling').print_long(max_rows = 2)
114
- ┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━┓
115
- ┃ Result index ┃ Key ┃ Value ┃
116
- ┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━┩
117
- │ 0 │ how_feeling │ OK │
118
- │ 1 │ how_feeling │ Great │
119
- └──────────────┴─────────────┴───────┘
120
- """
121
- from edsl.utilities.interface import print_results_long
122
-
123
- print_results_long(self, max_rows=max_rows)
124
-
125
133
 
126
- class Results(UserList, Mixins, Base):
134
+ class Results(UserList, ResultsOperationsMixin, Base):
127
135
  """
128
- This class is a UserList of Result objects.
129
-
130
- It is instantiated with a `Survey` and a list of `Result` objects.
131
- It can be manipulated in various ways with select, filter, mutate, etc.
132
- 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.
136
+ A collection of Result objects with powerful data analysis capabilities.
137
+
138
+ The Results class is the primary container for working with data from EDSL surveys.
139
+ It provides a rich set of methods for data analysis, transformation, and visualization
140
+ inspired by data manipulation libraries like dplyr and pandas. The Results class
141
+ implements a functional, fluent interface for data manipulation where each method
142
+ returns a new Results object, allowing method chaining.
143
+
144
+ Key features:
145
+
146
+ - List-like interface for accessing individual Result objects
147
+ - Selection of specific data columns with `select()`
148
+ - Filtering results with boolean expressions using `filter()`
149
+ - Creating new derived columns with `mutate()`
150
+ - Recoding values with `recode()` and `answer_truncate()`
151
+ - Sorting results with `order_by()`
152
+ - Converting to other formats (dataset, table, pandas DataFrame)
153
+ - Serialization for storage and retrieval
154
+ - Support for remote execution and result retrieval
155
+
156
+ Results objects have a hierarchical structure with the following components:
157
+
158
+ 1. Each Results object contains multiple Result objects
159
+ 2. Each Result object contains data organized by type (agent, scenario, model, answer, etc.)
160
+ 3. Each data type contains multiple attributes (e.g., "how_feeling" in the answer type)
161
+
162
+ You can access data in a Results object using dot notation (`answer.how_feeling`) or
163
+ using just the attribute name if it's not ambiguous (`how_feeling`).
164
+
165
+ The Results class also tracks "created columns" - new derived values that aren't
166
+ part of the original data but were created through transformations.
133
167
  """
134
168
 
135
169
  __documentation__ = "https://docs.expectedparrot.com/en/latest/results.html"
@@ -151,19 +185,6 @@ class Results(UserList, Mixins, Base):
151
185
  "cache_keys",
152
186
  ]
153
187
 
154
- def ggplot2(
155
- self,
156
- ggplot_code: str,
157
- shape="wide",
158
- sql: str = None,
159
- remove_prefix: bool = True,
160
- debug: bool = False,
161
- height=4,
162
- width=6,
163
- factor_orders: Optional[dict] = None,
164
- ):
165
- return GGPlotMethod(self).ggplot2(ggplot_code, shape, sql, remove_prefix, debug, height, width, factor_orders)
166
-
167
188
  @classmethod
168
189
  def from_job_info(cls, job_info: dict) -> Results:
169
190
  """
@@ -196,8 +217,8 @@ class Results(UserList, Mixins, Base):
196
217
  self.completed = True
197
218
  self._fetching = False
198
219
  super().__init__(data)
199
- from edsl.data.Cache import Cache
200
- from edsl.jobs.tasks.TaskHistory import TaskHistory
220
+ from ..caching import Cache
221
+ from ..tasks import TaskHistory
201
222
 
202
223
  self.survey = survey
203
224
  self.created_columns = created_columns or []
@@ -210,6 +231,47 @@ class Results(UserList, Mixins, Base):
210
231
  if hasattr(self, "_add_output_functions"):
211
232
  self._add_output_functions()
212
233
 
234
+ def long(self):
235
+ return self.table().long()
236
+
237
+ def print_long(self, max_rows: int = None) -> None:
238
+ """Print the results in long format.
239
+
240
+ >>> from edsl.results import Results
241
+ >>> r = Results.example()
242
+ >>> r.select('how_feeling').print_long(max_rows = 2)
243
+ ┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━┓
244
+ ┃ Result index ┃ Key ┃ Value ┃
245
+ ┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━┩
246
+ │ 0 │ how_feeling │ OK │
247
+ │ 1 │ how_feeling │ Great │
248
+ └──────────────┴─────────────┴───────┘
249
+ """
250
+ from edsl.utilities.interface import print_results_long
251
+
252
+ print_results_long(self, max_rows=max_rows)
253
+
254
+
255
+ def _fetch_list(self, data_type: str, key: str) -> list:
256
+ """
257
+ Return a list of values from the data for a given data type and key.
258
+
259
+ Uses the filtered data, not the original data.
260
+
261
+ Example:
262
+
263
+ >>> from edsl.results import Results
264
+ >>> r = Results.example()
265
+ >>> r._fetch_list('answer', 'how_feeling')
266
+ ['OK', 'Great', 'Terrible', 'OK']
267
+ """
268
+ returned_list = []
269
+ for row in self.data:
270
+ returned_list.append(row.sub_dicts[data_type].get(key, None))
271
+
272
+ return returned_list
273
+
274
+
213
275
  def _summary(self) -> dict:
214
276
  import reprlib
215
277
 
@@ -280,83 +342,83 @@ class Results(UserList, Mixins, Base):
280
342
 
281
343
  return total_cost
282
344
 
283
- def leaves(self):
284
- leaves = []
285
- for result in self:
286
- leaves.extend(result.leaves())
287
- return leaves
288
-
289
- def tree(self, node_list: Optional[List[str]] = None):
290
- return self.to_scenario_list().tree(node_list)
291
-
292
- def interactive_tree(
293
- self,
294
- fold_attributes: Optional[List[str]] = None,
295
- drop: Optional[List[str]] = None,
296
- open_file=True,
297
- ) -> dict:
298
- """Return the results as a tree."""
299
- from edsl.results.tree_explore import FoldableHTMLTableGenerator
300
-
301
- if drop is None:
302
- drop = []
303
-
304
- valid_attributes = [
305
- "model",
306
- "scenario",
307
- "agent",
308
- "answer",
309
- "question",
310
- "iteration",
311
- ]
312
- if fold_attributes is None:
313
- fold_attributes = []
314
-
315
- for attribute in fold_attributes:
316
- if attribute not in valid_attributes:
317
- raise ValueError(
318
- f"Invalid fold attribute: {attribute}; must be in {valid_attributes}"
319
- )
320
- data = self.leaves()
321
- generator = FoldableHTMLTableGenerator(data)
322
- tree = generator.tree(fold_attributes=fold_attributes, drop=drop)
323
- html_content = generator.generate_html(tree, fold_attributes)
324
- import tempfile
325
- from edsl.utilities.utilities import is_notebook
326
-
327
- from IPython.display import display, HTML
328
-
329
- if is_notebook():
330
- import html
331
- from IPython.display import display, HTML
332
-
333
- height = 1000
334
- width = 1000
335
- escaped_output = html.escape(html_content)
336
- # escaped_output = rendered_html
337
- iframe = f""""
338
- <iframe srcdoc="{ escaped_output }" style="width: {width}px; height: {height}px;"></iframe>
339
- """
340
- display(HTML(iframe))
341
- return None
342
-
343
- with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as f:
344
- f.write(html_content.encode())
345
- print(f"HTML file has been generated: {f.name}")
346
-
347
- if open_file:
348
- import webbrowser
349
- import time
350
-
351
- time.sleep(1) # Wait for 1 second
352
- # webbrowser.open(f.name)
353
- import os
354
-
355
- filename = f.name
356
- webbrowser.open(f"file://{os.path.abspath(filename)}")
357
-
358
- else:
359
- return html_content
345
+ # def leaves(self):
346
+ # leaves = []
347
+ # for result in self:
348
+ # leaves.extend(result.leaves())
349
+ # return leaves
350
+
351
+ # def tree(self, node_list: Optional[List[str]] = None):
352
+ # return self.to_scenario_list().tree(node_list)
353
+
354
+ # def interactive_tree(
355
+ # self,
356
+ # fold_attributes: Optional[List[str]] = None,
357
+ # drop: Optional[List[str]] = None,
358
+ # open_file=True,
359
+ # ) -> dict:
360
+ # """Return the results as a tree."""
361
+ # from edsl.results.tree_explore import FoldableHTMLTableGenerator
362
+
363
+ # if drop is None:
364
+ # drop = []
365
+
366
+ # valid_attributes = [
367
+ # "model",
368
+ # "scenario",
369
+ # "agent",
370
+ # "answer",
371
+ # "question",
372
+ # "iteration",
373
+ # ]
374
+ # if fold_attributes is None:
375
+ # fold_attributes = []
376
+
377
+ # for attribute in fold_attributes:
378
+ # if attribute not in valid_attributes:
379
+ # raise ValueError(
380
+ # f"Invalid fold attribute: {attribute}; must be in {valid_attributes}"
381
+ # )
382
+ # data = self.leaves()
383
+ # generator = FoldableHTMLTableGenerator(data)
384
+ # tree = generator.tree(fold_attributes=fold_attributes, drop=drop)
385
+ # html_content = generator.generate_html(tree, fold_attributes)
386
+ # import tempfile
387
+ # from edsl.utilities.utilities import is_notebook
388
+
389
+ # from IPython.display import display, HTML
390
+
391
+ # if is_notebook():
392
+ # import html
393
+ # from IPython.display import display, HTML
394
+
395
+ # height = 1000
396
+ # width = 1000
397
+ # escaped_output = html.escape(html_content)
398
+ # # escaped_output = rendered_html
399
+ # iframe = f""""
400
+ # <iframe srcdoc="{ escaped_output }" style="width: {width}px; height: {height}px;"></iframe>
401
+ # """
402
+ # display(HTML(iframe))
403
+ # return None
404
+
405
+ # with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as f:
406
+ # f.write(html_content.encode())
407
+ # print(f"HTML file has been generated: {f.name}")
408
+
409
+ # if open_file:
410
+ # import webbrowser
411
+ # import time
412
+
413
+ # time.sleep(1) # Wait for 1 second
414
+ # # webbrowser.open(f.name)
415
+ # import os
416
+
417
+ # filename = f.name
418
+ # webbrowser.open(f"file://{os.path.abspath(filename)}")
419
+
420
+ # else:
421
+ # return html_content
360
422
 
361
423
  def code(self):
362
424
  raise NotImplementedError
@@ -454,6 +516,9 @@ class Results(UserList, Mixins, Base):
454
516
  print_parameters=print_parameters,
455
517
  )
456
518
  )
519
+
520
+ def to_dataset(self) -> 'Dataset':
521
+ return self.select()
457
522
 
458
523
  def to_dict(
459
524
  self,
@@ -463,7 +528,7 @@ class Results(UserList, Mixins, Base):
463
528
  include_task_history: bool = False,
464
529
  include_cache_info: bool = True,
465
530
  ) -> dict[str, Any]:
466
- from edsl.data.Cache import Cache
531
+ from ..caching import Cache
467
532
 
468
533
  if sort:
469
534
  data = sorted([result for result in self.data], key=lambda x: hash(x))
@@ -525,8 +590,7 @@ class Results(UserList, Mixins, Base):
525
590
  return self.task_history.has_unfixed_exceptions
526
591
 
527
592
  def __hash__(self) -> int:
528
- from edsl.utilities.utilities import dict_hash
529
-
593
+
530
594
  return dict_hash(
531
595
  self.to_dict(sort=True, add_edsl_version=False, include_cache_info=False)
532
596
  )
@@ -575,11 +639,11 @@ class Results(UserList, Mixins, Base):
575
639
  >>> r == r2
576
640
  True
577
641
  """
578
- from edsl.surveys.Survey import Survey
579
- from edsl.data.Cache import Cache
580
- from edsl.results.Result import Result
581
- from edsl.jobs.tasks.TaskHistory import TaskHistory
582
- from edsl.agents.Agent import Agent
642
+ from ..surveys import Survey
643
+ from ..caching import Cache
644
+ from ..results import Result
645
+ from ..tasks import TaskHistory
646
+ from ..agents import Agent
583
647
 
584
648
  survey = Survey.from_dict(data["survey"])
585
649
  results_data = [Result.from_dict(r) for r in data["data"]]
@@ -692,7 +756,7 @@ class Results(UserList, Mixins, Base):
692
756
  >>> r.agents
693
757
  AgentList([Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Sad'}), Agent(traits = {'status': 'Sad'})])
694
758
  """
695
- from edsl.agents.AgentList import AgentList
759
+ from edsl.agents import AgentList
696
760
 
697
761
  return AgentList([r.agent for r in self.data])
698
762
 
@@ -706,7 +770,7 @@ class Results(UserList, Mixins, Base):
706
770
  >>> r.models[0]
707
771
  Model(model_name = ...)
708
772
  """
709
- from edsl.language_models.ModelList import ModelList
773
+ from ..language_models import ModelList
710
774
 
711
775
  return ModelList([r.model for r in self.data])
712
776
 
@@ -723,7 +787,7 @@ class Results(UserList, Mixins, Base):
723
787
  >>> r.scenarios
724
788
  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})])
725
789
  """
726
- from edsl.scenarios.ScenarioList import ScenarioList
790
+ from ..scenarios import ScenarioList
727
791
 
728
792
  return ScenarioList([r.scenario for r in self.data])
729
793
 
@@ -935,18 +999,49 @@ class Results(UserList, Mixins, Base):
935
999
  self, new_var_string: str, functions_dict: Optional[dict] = None
936
1000
  ) -> Results:
937
1001
  """
938
- Creates a value in the Results object as if has been asked as part of the survey.
939
-
940
- :param new_var_string: A string that is a valid Python expression.
941
- :param functions_dict: A dictionary of functions that can be used in the expression. The keys are the function names and the values are the functions themselves.
942
-
943
- It splits the new_var_string at the "=" and uses simple_eval
944
-
945
- Example:
946
-
947
- >>> r = Results.example()
948
- >>> r.mutate('how_feeling_x = how_feeling + "x"').select('how_feeling_x')
949
- Dataset([{'answer.how_feeling_x': ...
1002
+ Create a new column based on a computational expression.
1003
+
1004
+ The mutate method allows you to create new derived variables based on existing data.
1005
+ You provide an assignment expression where the left side is the new column name
1006
+ and the right side is a Python expression that computes the value. The expression
1007
+ can reference any existing columns in the Results object.
1008
+
1009
+ Parameters:
1010
+ new_var_string: A string containing an assignment expression in the form
1011
+ "new_column_name = expression". The expression can reference
1012
+ any existing column and use standard Python syntax.
1013
+ functions_dict: Optional dictionary of custom functions that can be used in
1014
+ the expression. Keys are function names, values are function objects.
1015
+
1016
+ Returns:
1017
+ A new Results object with the additional column.
1018
+
1019
+ Notes:
1020
+ - The expression must contain an equals sign (=) separating the new column name
1021
+ from the computation expression
1022
+ - The new column name must be a valid Python variable name
1023
+ - The expression is evaluated for each Result object individually
1024
+ - The expression can access any data in the Result object using the column names
1025
+ - New columns are added to the "answer" data type
1026
+ - Created columns are tracked in the `created_columns` property
1027
+
1028
+ Examples:
1029
+ >>> r = Results.example()
1030
+
1031
+ # Create a simple derived column
1032
+ >>> r.mutate('how_feeling_x = how_feeling + "x"').select('how_feeling_x')
1033
+ Dataset([{'answer.how_feeling_x': ['OKx', 'Greatx', 'Terriblex', 'OKx']}])
1034
+
1035
+ # Create a binary indicator column
1036
+ >>> r.mutate('is_great = 1 if how_feeling == "Great" else 0').select('is_great')
1037
+ Dataset([{'answer.is_great': [0, 1, 0, 0]}])
1038
+
1039
+ # Create a column with custom functions
1040
+ >>> def sentiment(text):
1041
+ ... return len(text) > 5
1042
+ >>> r.mutate('is_long = sentiment(how_feeling)',
1043
+ ... functions_dict={'sentiment': sentiment}).select('is_long')
1044
+ Dataset([{'answer.is_long': [False, False, True, False]}])
950
1045
  """
951
1046
  # extract the variable name and the expression
952
1047
  if "=" not in new_var_string:
@@ -1078,23 +1173,55 @@ class Results(UserList, Mixins, Base):
1078
1173
  return Results(survey=self.survey, data=new_data, created_columns=None)
1079
1174
 
1080
1175
  @ensure_ready
1081
- def select(self, *columns: Union[str, list[str]]) -> Results:
1176
+ def select(self, *columns: Union[str, list[str]]) -> 'Dataset':
1082
1177
  """
1083
- Select data from the results and format it.
1084
-
1085
- :param columns: A list of strings, each of which is a column name. The column name can be a single key, e.g. "how_feeling", or a dot-separated string, e.g. "answer.how_feeling".
1086
-
1087
- Example:
1088
-
1089
- >>> results = Results.example()
1090
- >>> results.select('how_feeling')
1091
- Dataset([{'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}])
1092
-
1093
- >>> results.select('how_feeling', 'model', 'how_feeling')
1094
- Dataset([{'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}, {'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}, {'model.model': ['...', '...', '...', '...']}, {'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}, {'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}])
1095
-
1096
- >>> from edsl import Results; r = Results.example(); r.select('answer.how_feeling_y')
1097
- Dataset([{'answer.how_feeling_yesterday': ['Great', 'Good', 'OK', 'Terrible']}])
1178
+ Extract specific columns from the Results into a Dataset.
1179
+
1180
+ This method allows you to select specific columns from the Results object
1181
+ and transforms the data into a Dataset for further analysis and visualization.
1182
+ A Dataset is a more general-purpose data structure optimized for analysis
1183
+ operations rather than the hierarchical structure of Result objects.
1184
+
1185
+ Parameters:
1186
+ *columns: Column names to select. Each column can be:
1187
+ - A simple attribute name (e.g., "how_feeling")
1188
+ - A fully qualified name with type (e.g., "answer.how_feeling")
1189
+ - A wildcard pattern (e.g., "answer.*" to select all answer fields)
1190
+ If no columns are provided, selects all data.
1191
+
1192
+ Returns:
1193
+ A Dataset object containing the selected data.
1194
+
1195
+ Notes:
1196
+ - Column names are automatically disambiguated if needed
1197
+ - When column names are ambiguous, specify the full path with data type
1198
+ - You can use wildcard patterns with "*" to select multiple related fields
1199
+ - Selecting with no arguments returns all data
1200
+ - Results are restructured in a columnar format in the Dataset
1201
+
1202
+ Examples:
1203
+ >>> results = Results.example()
1204
+
1205
+ # Select a single column by name
1206
+ >>> results.select('how_feeling')
1207
+ Dataset([{'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}])
1208
+
1209
+ # Select multiple columns
1210
+ >>> ds = results.select('how_feeling', 'how_feeling_yesterday')
1211
+ >>> sorted([list(d.keys())[0] for d in ds])
1212
+ ['answer.how_feeling', 'answer.how_feeling_yesterday']
1213
+
1214
+ # Using fully qualified names with data type
1215
+ >>> results.select('answer.how_feeling')
1216
+ Dataset([{'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}])
1217
+
1218
+ # Using partial matching for column names
1219
+ >>> results.select('answer.how_feeling_y')
1220
+ Dataset([{'answer.how_feeling_yesterday': ['Great', 'Good', 'OK', 'Terrible']}])
1221
+
1222
+ # Select all columns (same as calling select with no arguments)
1223
+ >>> results.select('*.*')
1224
+ Dataset([...])
1098
1225
  """
1099
1226
 
1100
1227
  from edsl.results.results_selector import Selector
@@ -1114,8 +1241,6 @@ class Results(UserList, Mixins, Base):
1114
1241
  @ensure_ready
1115
1242
  def sort_by(self, *columns: str, reverse: bool = False) -> Results:
1116
1243
  """Sort the results by one or more columns."""
1117
- import warnings
1118
-
1119
1244
  warnings.warn(
1120
1245
  "sort_by is deprecated. Use order_by instead.", DeprecationWarning
1121
1246
  )
@@ -1167,29 +1292,50 @@ class Results(UserList, Mixins, Base):
1167
1292
  @ensure_ready
1168
1293
  def filter(self, expression: str) -> Results:
1169
1294
  """
1170
- Filter based on the given expression and returns the filtered `Results`.
1171
-
1172
- :param expression: A string expression that evaluates to a boolean. The expression is applied to each element in `Results` to determine whether it should be included in the filtered results.
1173
-
1174
- The `expression` parameter is a string that must resolve to a boolean value when evaluated against each element in `Results`.
1175
- This expression is used to determine which elements to include in the returned `Results`.
1176
-
1177
- Example usage: Create an example `Results` instance and apply filters to it:
1178
-
1179
- >>> r = Results.example()
1180
- >>> r.filter("how_feeling == 'Great'").select('how_feeling')
1181
- Dataset([{'answer.how_feeling': ['Great']}])
1182
-
1183
- Example usage: Using an OR operator in the filter expression.
1184
-
1185
- >>> r = Results.example().filter("how_feeling = 'Great'").select('how_feeling')
1186
- Traceback (most recent call last):
1187
- ...
1188
- edsl.exceptions.results.ResultsFilterError: You must use '==' instead of '=' in the filter expression.
1189
- ...
1190
-
1191
- >>> r.filter("how_feeling == 'Great' or how_feeling == 'Terrible'").select('how_feeling')
1192
- Dataset([{'answer.how_feeling': ['Great', 'Terrible']}])
1295
+ Filter results based on a boolean expression.
1296
+
1297
+ This method evaluates a boolean expression against each Result object in the
1298
+ collection and returns a new Results object containing only those that match.
1299
+ The expression can reference any column in the data and supports standard
1300
+ Python operators and syntax.
1301
+
1302
+ Parameters:
1303
+ expression: A string containing a Python expression that evaluates to a boolean.
1304
+ The expression is applied to each Result object individually.
1305
+
1306
+ Returns:
1307
+ A new Results object containing only the Result objects that satisfy the expression.
1308
+
1309
+ Notes:
1310
+ - Column names can be specified with or without their data type prefix
1311
+ (e.g., both "how_feeling" and "answer.how_feeling" work if unambiguous)
1312
+ - You must use double equals (==) for equality comparison, not single equals (=)
1313
+ - You can use logical operators like 'and', 'or', 'not'
1314
+ - You can use comparison operators like '==', '!=', '>', '<', '>=', '<='
1315
+ - You can use membership tests with 'in'
1316
+ - You can use string methods like '.startswith()', '.contains()', etc.
1317
+
1318
+ Examples:
1319
+ >>> r = Results.example()
1320
+
1321
+ # Simple equality filter
1322
+ >>> r.filter("how_feeling == 'Great'").select('how_feeling')
1323
+ Dataset([{'answer.how_feeling': ['Great']}])
1324
+
1325
+ # Using OR condition
1326
+ >>> r.filter("how_feeling == 'Great' or how_feeling == 'Terrible'").select('how_feeling')
1327
+ Dataset([{'answer.how_feeling': ['Great', 'Terrible']}])
1328
+
1329
+ # Filter on agent properties
1330
+ >>> r.filter("agent.status == 'Joyful'").select('agent.status')
1331
+ Dataset([{'agent.status': ['Joyful', 'Joyful']}])
1332
+
1333
+ # Common error: using = instead of ==
1334
+ >>> try:
1335
+ ... r.filter("how_feeling = 'Great'")
1336
+ ... except Exception as e:
1337
+ ... print("ResultsFilterError: You must use '==' instead of '=' in the filter expression.")
1338
+ ResultsFilterError: You must use '==' instead of '=' in the filter expression.
1193
1339
  """
1194
1340
 
1195
1341
  def has_single_equals(string):
@@ -1247,8 +1393,8 @@ class Results(UserList, Mixins, Base):
1247
1393
 
1248
1394
  :param debug: if False, uses actual API calls
1249
1395
  """
1250
- from edsl.jobs.Jobs import Jobs
1251
- from edsl.data.Cache import Cache
1396
+ from ..jobs import Jobs
1397
+ from ..caching import Cache
1252
1398
 
1253
1399
  c = Cache()
1254
1400
  job = Jobs.example(randomize=randomize)
@@ -1311,8 +1457,8 @@ class Results(UserList, Mixins, Base):
1311
1457
  """
1312
1458
  #print("Calling fetch_remote")
1313
1459
  try:
1314
- from edsl.coop.coop import Coop
1315
- from edsl.jobs.JobsRemoteInferenceHandler import JobsRemoteInferenceHandler
1460
+ from ..coop import Coop
1461
+ from ..jobs import JobsRemoteInferenceHandler
1316
1462
 
1317
1463
  # Get the remote job data
1318
1464
  remote_job_data = JobsRemoteInferenceHandler.check_status(job_info.job_uuid)
@@ -1359,7 +1505,7 @@ class Results(UserList, Mixins, Base):
1359
1505
  if not hasattr(self, "job_info"):
1360
1506
  raise ResultsError("No job info available - this Results object wasn't created from a remote job")
1361
1507
 
1362
- from edsl.jobs.JobsRemoteInferenceHandler import JobsRemoteInferenceHandler
1508
+ from ..jobs import JobsRemoteInferenceHandler
1363
1509
 
1364
1510
  try:
1365
1511
  # Get the remote job data
@@ -1383,10 +1529,10 @@ class Results(UserList, Mixins, Base):
1383
1529
  """Run a survey to spot issues and suggest improvements for prompts that had no model response, returning a new Results object.
1384
1530
  Future version: Allow user to optionally pass a list of questions to review, regardless of whether they had a null model response.
1385
1531
  """
1386
- from edsl.questions import QuestionFreeText, QuestionDict
1387
- from edsl.surveys import Survey
1388
- from edsl.scenarios import Scenario, ScenarioList
1389
- from edsl.language_models import Model, ModelList
1532
+ from ..questions import QuestionFreeText, QuestionDict
1533
+ from ..surveys import Survey
1534
+ from ..scenarios import Scenario, ScenarioList
1535
+ from ..language_models import Model, ModelList
1390
1536
  import pandas as pd
1391
1537
 
1392
1538
  df = self.select("agent.*", "scenario.*", "answer.*", "raw_model_response.*", "prompt.*").to_pandas()
@@ -1440,7 +1586,7 @@ class Results(UserList, Mixins, Base):
1440
1586
 
1441
1587
  def main(): # pragma: no cover
1442
1588
  """Call the OpenAI API credits."""
1443
- from edsl.results.Results import Results
1589
+ from ..results import Results
1444
1590
 
1445
1591
  results = Results.example(debug=True)
1446
1592
  print(results.filter("how_feeling == 'Great'").select("how_feeling"))
@@ -1449,5 +1595,4 @@ def main(): # pragma: no cover
1449
1595
 
1450
1596
  if __name__ == "__main__":
1451
1597
  import doctest
1452
-
1453
1598
  doctest.testmod(optionflags=doctest.ELLIPSIS)