{ "info": { "author": "Benjamin T. Vincent", "author_email": "b.t.vincent@dundee.ac.uk", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3" ], "description": "# BADAPTED: Bayesian ADAPTive Experimental Design\n\n[![PyPI](https://img.shields.io/pypi/v/badapted.svg?color=green)](https://pypi.org/project/badapted/)\n\n**Status: Working code, but still under development \ud83d\udd25**\n\nRun efficient Bayesian adaptive experiments.\n\nThis code relates to the following pre-print. But, the pre-print is likely to appear in quite a different form when finally published.\n> Vincent, B. T., & Rainforth, T. (2017, October 20). The DARC Toolbox: automated, flexible, and efficient delayed and risky choice experiments using Bayesian adaptive design. Retrieved from psyarxiv.com/yehjb\n\n\n## Building your own adaptive experiment toolbox on top of `badapted`\n\nBelow we outline how the `badapted` package can be used to run adaptive experiments. On it's own, this `badapted` package will not do anything. It also requires a few classes (and probably some helper functions) that a developer must create for their particular experimental paradigm. This forms a 'toolbox' which will allow adaptive experiments to be run in a particular experimental domain.\n\nThe best (first) example of this is our [DARC Toolbox](https://github.com/drbenvincent/darc_toolbox) which allows adaptive experiments for Delayed And Risky Choice tasks.\n\nBut below we outline how to go about creating a new 'toolbox' for your experimental domain of interest.\n\n\n### Step 1: define your design space\n\nFirst we create a pandas dataframe called `designs` using a function we write to do this. Each column is a design variable. Each row is a particular design.\n\n```python\ndef build_my_design_space(my_arguments):\n designs = # CREATE PANDAS DATAFRAME OF THE DESIGN SPACE HERE\n return designs\n```\n\n### Step 2: define a custom design generator\n\nIn order to generate your own design generator that uses Bayesian Adaptive Design (in your experimental domain) then you need to create a class which subclasses `badapted.BayesianAdaptiveDesignGenerator`. You will also need to implement some methods specific to your experimental domain, notably:\n- `add_design_response_to_dataframe`\n- `df_to_design_tuple`\n\nFor the moment, we will just provide the example we use in the DARC Toolbox. Firstly, our concrete design generator class is defined as:\n\n```python\nfrom badapted.designs import BayesianAdaptiveDesignGenerator\n\nclass BayesianAdaptiveDesignGeneratorDARC(DARCDesignGenerator, BayesianAdaptiveDesignGenerator):\n '''This will be the concrete class for doing Bayesian adaptive design\n in the DARC experiment domain.'''\n\n def __init__(self, design_space,\n max_trials=20,\n allow_repeats=True,\n penalty_function_option='default',\n \u03bb=2):\n\n # call superclass constructors - note that the order of calling these is important\n BayesianAdaptiveDesignGenerator.__init__(self, design_space,\n max_trials=max_trials,\n allow_repeats=allow_repeats,\n penalty_function_option=penalty_function_option,\n \u03bb=\u03bb)\n\n DARCDesignGenerator.__init__(self)\n```\n\nNote that this has mulitple inheritance, so we also have a class `DARCDesignGenerator` which just includes DARC specific methods (`add_design_response_to_dataframe`, `df_to_design_tuple`). This is defined as:\n\n```python\nfrom badapted.designs import DesignGeneratorABC\nfrom darc_toolbox import Prospect, Design\n\n\nclass DARCDesignGenerator(DesignGeneratorABC):\n '''This adds DARC specific functionality to the design generator'''\n\n def __init__(self):\n # super().__init__()\n DesignGeneratorABC.__init__(self)\n\n # generate empty dataframe\n data_columns = ['RA', 'DA', 'PA', 'RB', 'DB', 'PB', 'R']\n self.data = pd.DataFrame(columns=data_columns)\n\n def add_design_response_to_dataframe(self, design, response):\n '''\n This method must take in `design` and `reward` from the current trial\n and store this as a new row in self.data which is a pandas data frame.\n '''\n\n trial_data = {'RA': design.ProspectA.reward,\n 'DA': design.ProspectA.delay,\n 'PA': design.ProspectA.prob,\n 'RB': design.ProspectB.reward,\n 'DB': design.ProspectB.delay,\n 'PB': design.ProspectB.prob,\n 'R': [int(response)]}\n self.data = self.data.append(pd.DataFrame(trial_data))\n # a bit clumsy but...\n self.data['R'] = self.data['R'].astype('int64')\n self.data = self.data.reset_index(drop=True)\n return\n\n @staticmethod\n def df_to_design_tuple(df):\n '''User must impliment this method. It takes in a design in the form of a\n single row of pandas dataframe, and it must return the chosen design as a\n named tuple.\n Convert 1-row pandas dataframe into named tuple'''\n RA = df.RA.values[0]\n DA = df.DA.values[0]\n PA = df.PA.values[0]\n RB = df.RB.values[0]\n DB = df.DB.values[0]\n PB = df.PB.values[0]\n chosen_design = Design(ProspectA=Prospect(reward=RA, delay=DA, prob=PA),\n ProspectB=Prospect(reward=RB, delay=DB, prob=PB))\n return chosen_design\n```\n\nWe only did this multiple inheritance because we wanted other (non Bayesian Adaptive) design generators which worked in the DARC domain, but did not have any of the Bayesian Adaptive Design components. In most situations just focussing on Bayesian Adaptive Design, you could just define the `add_design_response_to_dataframe`, `df_to_design_tuple` classes in your one single concrete design generator class.\n\n\n### Step 3: define a model\n\nYou must provide a model class which inherits from `Model`. You must also provide the following methods:\n\n- `__init__`\n- `predictive_y`\n\nHere is an example of a minimal implimentation of a user-defined model:\n\n```python\nfrom badapted.model import Model\nfrom badapted.choice_functions import CumulativeNormalChoiceFunc, StandardCumulativeNormalChoiceFunc\nfrom scipy.stats import norm, halfnorm, uniform\nimport numpy as np\n\n\nclass MyCustomModel(Model):\n '''My custom model which does XYZ.'''\n\n def __init__(self, n_particles,\n prior={'logk': norm(loc=-4.5, scale=1),\n '\u03b1': halfnorm(loc=0, scale=2)}):\n '''\n INPUTS\n - n_particles (integer).\n - prior (dictionary). The keys provide the parameter name. The values\n must be scipy.stats objects which define the prior distribution for\n this parameter.\n\n We provide choice functions in `badapted.choice_functions.py`. In this\n example, we define it in the __init__ but it is not necessary to happen\n here.\n '''\n self.n_particles = int(n_particles)\n self.prior = prior\n self.\u03b8_fixed = {'\u03f5': 0.01}\n self.choiceFunction = CumulativeNormalChoiceFunc\n\n def predictive_y(self, \u03b8, data):\n '''\n INPUTS:\n - \u03b8 = parameters\n - data =\n\n OUTPUT:\n - p_chose_B (float) Must return a value between 0-1.\n '''\n\n # Step 1 - calculate decision variable\n k = np.exp(\u03b8['logk'].values\n VA = data['RA'].values * 1 / (1 + k * data['DA'].values)\n VB = data['RB'].values * 1 / (1 + k * data['DB'].values)\n decision_variable = VB - VA\n\n # Step 2 - apply choice function\n p_chose_B = self.choiceFunction(decision_variable, \u03b8, self.\u03b8_fixed)\n return p_chose_B\n```\n\n### Step 4: build an experiment trial loop\n\nThis is pretty straight-forward and there doesn't need to be any major customisation here.\n\n```python\ndef run_experiment(design_generator, model, max_trials):\n '''Run an adaptive experiment\n INPUTS:\n - design_generator: a class\n '''\n\n for trial in range(max_trials):\n design = design_generator.get_next_design(model)\n if design is None:\n break\n response = get_response(design)\n design_generator.enter_trial_design_and_response(design, response)\n model.update_beliefs(design_generator.data)\n\n return model\n```\n\nNote that the `response = get_response(design)` line is up to you to impliment. What you do here depends on whether you are simulating responses or getting real responses from PsychoPy etc. The `run_experiment` function is just an example of how the various parts of the code work together. When running _actual_ experiments using PsychoPy, it is best to refer to the demo psychopy files we provide in the [DARC Toolbox](https://github.com/drbenvincent/darc_toolbox) as examples to see how this is done.\n\n### Step 5: setup and run the experiment\n\n```python\ndesigns = build_my_design_space(my_arguments)\ndesign_generator = MyCustomDesignGenerator(designs, max_trials=max_trials)\nmodel = MyCustomModel()\n\nmodel = run_experiment(design_generator, model, max_trials)\n```\n\nNote that use of the `run_experiment` function is just a demonstration of the logic of how things fit together. As mentioned, please refer to PsychoPy example experiments in the [DARC Toolbox](https://github.com/drbenvincent/darc_toolbox) to see how this all comes together in a PsychoPy experiment.\n\n\n## Toolboxes using `badapted`\n- [DARC Toolbox](https://github.com/drbenvincent/darc_toolbox) for adpative Delayed and Risky Choice tasks.\n- [Adaptive Psychophysics Toolbox](https://github.com/drbenvincent/adaptive_psychophysics_toolbox) for psychophysical experiments. _[Note: still under active development.]_\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/drbenvincent/badapted", "keywords": "bayesian,adaptive design,inference,optimisation", "license": "", "maintainer": "", "maintainer_email": "", "name": "badapted", "package_url": "https://pypi.org/project/badapted/", "platform": "", "project_url": "https://pypi.org/project/badapted/", "project_urls": { "Homepage": "https://github.com/drbenvincent/badapted" }, "release_url": "https://pypi.org/project/badapted/0.0.3/", "requires_dist": [ "numpy", "scipy", "pandas", "matplotlib" ], "requires_python": "", "summary": "Bayesian ADAPTive Experimental Design", "version": "0.0.3" }, "last_serial": 5761005, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "e30158591ca39f341e724ee40a74596d", "sha256": "860529404b211ec59d34f4f776cfee76dfd25c2ab63128e3e4030a01ae63585c" }, "downloads": -1, "filename": "badapted-0.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "e30158591ca39f341e724ee40a74596d", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 16937, "upload_time": "2019-08-13T12:09:59", "url": "https://files.pythonhosted.org/packages/62/9d/c2bfde5cda637f33445e90c867b95db5e5a68ccc92f5f3f0f133a0739db0/badapted-0.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d35711196b7171e2f1d6c2a11bb4d7cc", "sha256": "f06b103dd3785379d0d898f80e12ef459dcc7e60b76c385bdf9e4f49098d59a6" }, "downloads": -1, "filename": "badapted-0.0.1.tar.gz", "has_sig": false, "md5_digest": "d35711196b7171e2f1d6c2a11bb4d7cc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13637, "upload_time": "2019-08-13T12:10:01", "url": "https://files.pythonhosted.org/packages/ed/30/333b60fbf93dd5a07d750b303401250766231cf2df7981ae8fd9e8420eb2/badapted-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "ca4a99b14cb7ce1f7902e5795d612df3", "sha256": "f78c449eb1f59f184299490d972e0ec21e3a67ebd870644e50784dc187173191" }, "downloads": -1, "filename": "badapted-0.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "ca4a99b14cb7ce1f7902e5795d612df3", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 16942, "upload_time": "2019-08-13T12:43:27", "url": "https://files.pythonhosted.org/packages/88/61/6076dedc5ee861aa619e7262e6c89f2b6698dcdcc4411df7139d09a08efe/badapted-0.0.2-py3-none-any.whl" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "17a22de7ac604a06b6562829f2d69a51", "sha256": "4cabe6344fc5d8350418b8bcd8b81bc014b42378c21cc824cb602ceeffaa0b85" }, "downloads": -1, "filename": "badapted-0.0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "17a22de7ac604a06b6562829f2d69a51", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 22212, "upload_time": "2019-08-30T16:00:18", "url": "https://files.pythonhosted.org/packages/37/61/a23de53fbb0e238f3faeec5311748f52295d0b2e29e49fa34a9bd8b9f194/badapted-0.0.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d7eeda8f23820e564be759f380a91a8a", "sha256": "33a610cd202a4746a387482be894c0d4860865f79781aed902a6b46b83de81a7" }, "downloads": -1, "filename": "badapted-0.0.3.tar.gz", "has_sig": false, "md5_digest": "d7eeda8f23820e564be759f380a91a8a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22441, "upload_time": "2019-08-30T16:00:20", "url": "https://files.pythonhosted.org/packages/90/15/79985965f3ce7843615a15b3330bf35a9e76eb6064cd991b037df9807977/badapted-0.0.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "17a22de7ac604a06b6562829f2d69a51", "sha256": "4cabe6344fc5d8350418b8bcd8b81bc014b42378c21cc824cb602ceeffaa0b85" }, "downloads": -1, "filename": "badapted-0.0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "17a22de7ac604a06b6562829f2d69a51", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 22212, "upload_time": "2019-08-30T16:00:18", "url": "https://files.pythonhosted.org/packages/37/61/a23de53fbb0e238f3faeec5311748f52295d0b2e29e49fa34a9bd8b9f194/badapted-0.0.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d7eeda8f23820e564be759f380a91a8a", "sha256": "33a610cd202a4746a387482be894c0d4860865f79781aed902a6b46b83de81a7" }, "downloads": -1, "filename": "badapted-0.0.3.tar.gz", "has_sig": false, "md5_digest": "d7eeda8f23820e564be759f380a91a8a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22441, "upload_time": "2019-08-30T16:00:20", "url": "https://files.pythonhosted.org/packages/90/15/79985965f3ce7843615a15b3330bf35a9e76eb6064cd991b037df9807977/badapted-0.0.3.tar.gz" } ] }