{"id":16536281,"url":"https://github.com/tillahoffmann/idxhound","last_synced_at":"2026-05-14T21:03:43.257Z","repository":{"id":37659752,"uuid":"265967294","full_name":"tillahoffmann/idxhound","owner":"tillahoffmann","description":"🐶 Track indices across one or more numpy selections.","archived":false,"fork":false,"pushed_at":"2022-12-08T09:59:59.000Z","size":38,"stargazers_count":1,"open_issues_count":7,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-30T23:46:01.794Z","etag":null,"topics":["data","numpy","scientific-computing"],"latest_commit_sha":null,"homepage":"https://idxhound.readthedocs.io/en/latest/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tillahoffmann.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-21T22:47:31.000Z","updated_at":"2023-03-16T10:47:29.000Z","dependencies_parsed_at":"2023-01-25T06:30:16.441Z","dependency_job_id":null,"html_url":"https://github.com/tillahoffmann/idxhound","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/tillahoffmann/idxhound","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tillahoffmann%2Fidxhound","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tillahoffmann%2Fidxhound/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tillahoffmann%2Fidxhound/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tillahoffmann%2Fidxhound/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tillahoffmann","download_url":"https://codeload.github.com/tillahoffmann/idxhound/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tillahoffmann%2Fidxhound/sbom","scorecard":{"id":885312,"data":{"date":"2025-08-11","repo":{"name":"github.com/tillahoffmann/idxhound","commit":"aadce45ad4eb26f23dc782a370cf3d9a054e0a16"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.2,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/9 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/codeql-analysis.yml:1","Warn: no topLevel permission defined: .github/workflows/pythonpackage.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/tillahoffmann/idxhound/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:32: update your workflow using https://app.stepsecurity.io/secureworkflow/tillahoffmann/idxhound/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/tillahoffmann/idxhound/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:54: update your workflow using https://app.stepsecurity.io/secureworkflow/tillahoffmann/idxhound/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pythonpackage.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/tillahoffmann/idxhound/pythonpackage.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pythonpackage.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/tillahoffmann/idxhound/pythonpackage.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/pythonpackage.yml:38: update your workflow using https://app.stepsecurity.io/secureworkflow/tillahoffmann/idxhound/pythonpackage.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/pythonpackage.yml:29","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   1 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":7,"reason":"SAST tool detected but not run on all commits","details":["Info: SAST configuration detected: CodeQL","Warn: 0 commits out of 7 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"29 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2021-421 / GHSA-h4m5-qpfp-3mpv","Warn: Project is vulnerable to: PYSEC-2021-865 / GHSA-vv2x-vrpj-qqpq","Warn: Project is vulnerable to: PYSEC-2022-42986 / GHSA-43fp-rhv2-5gv8","Warn: Project is vulnerable to: PYSEC-2023-135 / GHSA-xqr8-7jwr-rhp7","Warn: Project is vulnerable to: PYSEC-2024-60 / GHSA-jjg7-2v4v-x38h","Warn: Project is vulnerable to: GHSA-cpwx-vrp4-4pq7","Warn: Project is vulnerable to: PYSEC-2021-66 / GHSA-g3rq-g295-4j3m","Warn: Project is vulnerable to: GHSA-h5c8-rqwp-cp95","Warn: Project is vulnerable to: GHSA-h75v-3vvj-5mfj","Warn: Project is vulnerable to: GHSA-q2x7-8rv6-6q7h","Warn: Project is vulnerable to: PYSEC-2021-856 / GHSA-5545-2q6w-2gh6","Warn: Project is vulnerable to: GHSA-6p56-wp2h-9hxr","Warn: Project is vulnerable to: PYSEC-2021-857 / GHSA-f7c7-j99h-c22f","Warn: Project is vulnerable to: GHSA-fpfv-jqm9-f5jm","Warn: Project is vulnerable to: PYSEC-2020-92 / GHSA-hj5v-574p-mj7c","Warn: Project is vulnerable to: PYSEC-2022-42969","Warn: Project is vulnerable to: PYSEC-2021-140 / GHSA-9w8r-397f-prfh","Warn: Project is vulnerable to: PYSEC-2023-117 / GHSA-mrwq-x4v8-fh7p","Warn: Project is vulnerable to: PYSEC-2021-141 / GHSA-pq64-v7f5-gqh8","Warn: Project is vulnerable to: GHSA-9hjg-9r4m-mvj7","Warn: Project is vulnerable to: GHSA-9wx4-h78v-vm56","Warn: Project is vulnerable to: PYSEC-2023-74 / GHSA-j8r2-6x86-q33q","Warn: Project is vulnerable to: GHSA-g7vv-2v7x-gj9p","Warn: Project is vulnerable to: GHSA-34jh-p97f-mpxf","Warn: Project is vulnerable to: PYSEC-2023-212 / GHSA-g4mx-q9vg-27p4","Warn: Project is vulnerable to: GHSA-pq67-6m6q-mj2v","Warn: Project is vulnerable to: PYSEC-2021-108 / GHSA-q2q7-5pp4-w6pg","Warn: Project is vulnerable to: PYSEC-2023-192 / GHSA-v845-jxx5-vc9f","Warn: Project is vulnerable to: GHSA-jfmj-5v4g-7637"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T09:48:15.278Z","repository_id":37659752,"created_at":"2025-08-24T09:48:15.278Z","updated_at":"2025-08-24T09:48:15.278Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33043249,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data","numpy","scientific-computing"],"created_at":"2024-10-11T18:30:16.575Z","updated_at":"2026-05-14T21:03:43.240Z","avatar_url":"https://github.com/tillahoffmann.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"🐶 idxhound\n===========\n\n.. image:: https://github.com/tillahoffmann/idxhound/workflows/CI/badge.svg\n  :target: https://github.com/tillahoffmann/idxhound/actions?query=workflow%3A%22CI%22\n\n.. image:: https://img.shields.io/pypi/v/idxhound.svg?style=flat-square\n   :target: https://pypi.python.org/pypi/idxhound\n\n.. image:: https://readthedocs.org/projects/idxhound/badge/?version=latest\n  :target: https://idxhound.readthedocs.io/en/latest/?badge=latest\n\n``numpy`` provides outstanding indexing through its advanced indexing capabilities [1]_. ``idxhound`` tracks indices across one or more selections to make sure you always know where your data (in the form of array elements) came from.\n\nAlternatives include :py:class:`pandas.Index` and :py:class:`xarray.DataArray` which allow for indices other than monotonic integers. But sometimes one just wants to deal with the raw data arrays, e.g. to avoid any impact on performance or integrate with third-party libraries that expect raw numpy arrays. That's where ``idxhound`` can help.\n\nGetting started\n---------------\n\nObtaining a :py:class:`idxhound.Selection` object is straightforward: simply pass a selection object (such as a boolean filter or array of integer indices) as an argument to the constructor. For example, let's create an array and filter it using a boolean selection.\n\n\u003e\u003e\u003e x = np.asarray(list('abcdef'))\n\u003e\u003e\u003e obj = idxhound.Selection(x \u003e 'c')\n\u003e\u003e\u003e y = x[obj]\n\u003e\u003e\u003e y\narray(['d', 'e', 'f'], dtype='\u003cU1')\n\nThe indexing behaviour is exactly the same as if we'd used ``y = x[x \u003e 'c']``. But ``obj`` allows us to track where the elements in ``x`` ended up in ``y``. The example below illustrates how to find the index of ``x[3]`` in ``y``.\n\n\u003e\u003e\u003e i = obj[3]\n\u003e\u003e\u003e i, y[i]\n(0, 'd')\n\nBut indexing by an element that has been eliminated by the selection raises an error as one might expect.\n\n\u003e\u003e\u003e obj[2]\nTraceback (most recent call last):\n    ...\nKeyError: 2\n\nUsing the inverse of ``i`` allows us to retrieve the index of an element in ``x`` given its index in ``y``.\n\n\u003e\u003e\u003e j = obj.inverse[1]\n\u003e\u003e\u003e j, x[j], y[1]\n(4, 'e', 'e')\n\nConvenience functions\n^^^^^^^^^^^^^^^^^^^^^\n\nThe functions :py:func:`dict_to_array` and :py:func:`array_to_dict` facilitate conversion from dictionaries to arrays and vice versa. This functionality is convenient for loading or saving data with non-integer keys. Suppose we are presented with a dictionary of city populations which we want to convert to an array for manipulation.\n\n\u003e\u003e\u003e cities = ['Rome', 'Berlin', 'Paris', 'London']\n\u003e\u003e\u003e population = {'Rome': 2.873, 'Berlin': 3.769, 'London': 8.982}\n\u003e\u003e\u003e arr = idxhound.dict_to_array(population, idxhound.Selection(cities))\n\u003e\u003e\u003e arr\narray([2.873, 3.769,   nan, 8.982])\n\nConverting back to an array yields the following.\n\n\u003e\u003e\u003e idxhound.array_to_dict(arr, idxhound.Selection(cities))\n{'Rome': 2.873, 'Berlin': 3.769, 'Paris': nan, 'London': 8.982}\n\nThe two convienence functions are applicable to arrays with an arbitrary number of dimensions.\n\nAdvanced use\n------------\n\nWhile the above examples illustrate that ``idxhound`` can deliver what was promised, more advanced use cases is where it shines.\n\nComposition\n^^^^^^^^^^^\n\nSuppose we want to reorder and further filter the character sequence ``y`` but still keep track of indices across multiple selections. Easy!\n\n\u003e\u003e\u003e obj2 = idxhound.Selection([2, 0])\n\u003e\u003e\u003e y[obj2]\narray(['f', 'd'], dtype='\u003cU1')\n\nLet's construct a composite index that has the same effect as the sequential application of selections.\n\n\u003e\u003e\u003e composite = obj @ obj2  # use the `compose` method for python \u003c 3.5\n\u003e\u003e\u003e z = x[composite]\n\u003e\u003e\u003e z\narray(['f', 'd'], dtype='\u003cU1')\n\nSo where did the first element of ``z`` occur in ``x`` and ``y``, respectively?\n\n\u003e\u003e\u003e composite.inverse[0], obj2.inverse[0]\n(5, 2)\n\nNon-integer indices\n^^^^^^^^^^^^^^^^^^^\n\nReal data often use labels rather than integer indices (they might even be readable by humans if we're lucky). Suppose we have a simple dataset of populations of some European cities and we intend to order them.\n\n\u003e\u003e\u003e cities = ['Rome', 'Berlin', 'Paris', 'London']\n\u003e\u003e\u003e population = [2.873, 3.769, 2.148, 8.982]\n\u003e\u003e\u003e mapping = idxhound.Selection(cities)\n\u003e\u003e\u003e obj = (mapping @ np.argsort(population))\n\u003e\u003e\u003e obj[['London', 'Berlin']]\n[3, 2]\n\nLondon and Berlin would end up in last and second to last position in the ordered array, respectively. Indeed, they are the two largest cities. We can also easily retrieve the smallest city.\n\n\u003e\u003e\u003e obj.inverse[0]\n'Paris'\n\nNamed columns\n^^^^^^^^^^^^^\n\nBecause :py:class:`idxhound.Selection` is agnostic to the dimensions of the tensor being indexed, it can also be used to select named columns.\n\n\u003e\u003e\u003e latitude = [41.9028, 52.5200, 48.8566, 51.5074]\n\u003e\u003e\u003e longitude = [12.4964, 13.4050, 2.3522, 0.1278]\n\u003e\u003e\u003e data = np.transpose([population, latitude, longitude])\n\u003e\u003e\u003e columns = idxhound.Selection(['population', 'latitude', 'longitude'])\n\u003e\u003e\u003e data[mapping['Berlin'], columns[['latitude', 'longitude']]]\narray([52.52 , 13.405])\n\nProperties satisfied by ``Selection``\n-------------------------------------\n\nMore formally, an :py:class:`idxhound.Selection` satisfies the following properties. Let ``x`` be a one-dimensional array, ``idx`` be a selection that can be applied to ``x``, ``y = x[idx]``, and ``obj = idxhound.Selection(idx)``. Then\n\n1. indexing by ``obj`` is equivalent to indexing by ``idx``, i.e. all elements of ``y`` and ``x[obj]`` are equal,\n2. ``obj[i]`` retrieves the index of the element in ``y`` given its index ``i`` in ``x``, i.e. ``x[i] == y[obj[i]]``,\n3. and, conversely, ``obj.inverse[j]`` retrieves the index of the element in ``x`` given its index ``j`` in ``y``, i.e. ``x[obj.inverse[j]] == y[j]``.\n\n.. [1] Indexing.\n   https://numpy.org/doc/stable/reference/arrays.indexing.html#advanced-indexing\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftillahoffmann%2Fidxhound","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftillahoffmann%2Fidxhound","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftillahoffmann%2Fidxhound/lists"}