Skip to main content

mmap_ninja_dataframe

Memory-mapped dataframe abstraction based on mmap_ninja

Run tests:

uvx --with-editable . --with joblib --with zstandard --with dnn_cool_synthetic_dataset --with opencv-contrib-python --with transformers pytest

PropertyResult

PropertyResult is a sparse column of computed results, stored as two parallel mmaps under a directory:

  • index — a numpy mmap of the indices the results were computed for
  • results — an mmap-ninja mmap of the corresponding results ("numpy", "string", or "ragged")

results[j] is the result for index[j]. Because each result carries its own index, results can be recorded for any subset of indices, in any order.

Create and populate

Construct with an out_dir and the mmap_type for the results, then fill it with append / extend:

from mmap_ninja_dataframe import PropertyResult

pr = PropertyResult("my_store/sentiment", mmap_type="string")
pr.extend([0, 2], ["positive", "neutral"])   # results for indices 0 and 2
pr.append(1, "negative")                      # a single (index, result) pair

mmap_type selects the results mmap:

  • "string"StringsMmap (text labels, summaries, …)
  • "numpy" → numpy mmap (scores, token counts, fixed-shape embeddings, …)
  • "ragged"RaggedMmap (variable-length sequences)

Results can be recorded partially and out of order:

pr = PropertyResult("my_store/embedding", mmap_type="numpy")
pr.extend([5], [np.random.rand(768).astype(np.float32)])   # index 5 first
pr.extend([0, 1], [emb0, emb1])                            # earlier indices later

Recording an index that already has a result doesn't overwrite it in place — it appends a new (index, result) pair. Lookups always return the most recently recorded result for that index:

pr = PropertyResult("my_store/sentiment", mmap_type="string")
pr.append(2, "neutral")
pr.append(2, "positive")
pr[2]   # "positive" -- the latest write wins

Look up by index

pr[2]              # result recorded for index 2 (KeyError if absent)
pr.get(2)          # same, but returns default (None) if absent
pr.get(2, "n/a")   # with an explicit default
2 in pr            # whether index 2 has a result

pr[idx] = value is equivalent to pr.append(idx, value); pr[indices] = values (a sequence of indices) is equivalent to pr.extend(indices, values).

Inspect

len(pr)         # number of recorded (index, result) pairs -- not the number of *distinct* indices
pr.indices()    # the indices, in insertion order (may contain repeats -- see "latest wins" above)
pr.mmap_type    # "string" / "numpy" / "ragged"
pr.name         # basename of out_dir, e.g. "sentiment"
pr.out_dir      # the directory it's persisted under
pr.index        # raw numpy index mmap (None until the first write)
pr.results      # raw results mmap (None until the first write)

Reopen

mmap_type is required on every construction call, including when reopening — it is then validated against what's already on disk, and a mismatch raises ValueError:

pr = PropertyResult("my_store/sentiment", mmap_type="string")
# ValueError if "my_store/sentiment" already holds results of a different mmap_type.

TextPropertiesMmap

TextPropertiesMmap is a deduplicated store of texts annotated with named, independently-computed properties (sentiment, embeddings, summaries, …). Each unique text gets a stable integer index; each property is a PropertyResult keyed by those indices, supplied explicitly by the caller.

How text lookup works

Texts are stored as a StringsMmap (store.text). Alongside it, each text's sha256 content hash is stored in a fixed-width numpy memmap (store.content_hash, dtype "<U64"), used to detect duplicates and to resolve a text back to its index.

Content hashes are never loaded into memory as a Python dict. Instead, a second numpy memmap (content_hash_sorter) holds the permutation that sorts content_hash; it is fully recomputed every time the store is (re)opened or texts are appended. Looking up a hash is then a binary search — np.searchsorted(content_hash, target, sorter=sorter) for a single text, or one vectorized np.searchsorted call for a batch — instead of a linear scan or an in-memory dict.

Create and add texts

from mmap_ninja_dataframe import TextPropertiesMmap, PropertyResult

store = TextPropertiesMmap.from_texts(
    "my_store",
    texts=["The quick brown fox.", "Hello, world!"],
    properties=[PropertyResult("my_store/sentiment", mmap_type="string")],
)

# Add more texts, deduplicating by content hash. Returns the index for each
# input text: texts already in the store resolve to their existing index,
# duplicates within the same call resolve to the same new index, and only
# genuinely new texts get appended.
indices = store.update(["The quick brown fox.", "A new sentence."])
# indices == [0, 2]  -- "The quick brown fox." already existed at index 0

from_texts builds a fresh store from a list of texts. To reopen an existing one, construct TextPropertiesMmap directly with the same out_dir — properties are not auto-discovered from disk, so pass the same properties list again:

store = TextPropertiesMmap(
    "my_store",
    properties=[PropertyResult("my_store/sentiment", mmap_type="string")],
)

Record and read property results

The store behaves like a named collection of properties. Resolve texts to indices, then write to the property directly:

store["sentiment"].extend(indices, ["neutral", "positive"])

store["sentiment"]                            # the PropertyResult
store.get_property("sentiment")               # same thing
store.get_property_names()                    # ["sentiment"]
store.add_property(PropertyResult("my_store/summary", mmap_type="string"))
store["summary"] = PropertyResult("my_store/summary", mmap_type="string")   # equivalent to add_property
store.delete_property("summary")              # unregisters it and deletes its directory

Property names "text", "content_hash", and "content_hash_sorter" are reserved (used internally) and raise ValueError if registered.

Look up texts

store.index_of_text("Hello, world!")            # 1, or None if not present
store.indices_for_texts(["Hello, world!"])       # [1] -- vectorized; raises KeyError naming
                                                  # the first text not found in the store

store.get_text_properties("Hello, world!")
# {"unprocessed": ["sentiment"]}   -- no result recorded yet for this text/property

store.get_properties_for_texts(["The quick brown fox."])
# {"text": [...], "content_hash": array([...], dtype='<U64'), "idx": [0], "sentiment": ["neutral"]}
# Raises ValueError if any requested text is missing a result for any registered property.

Check progress

store.get_unprocessed_indices_for_property("sentiment")   # numpy array of indices with no result yet
store.get_unprocessed_counts()                             # {"sentiment": 1} -- only not-yet-complete properties

Inspect

len(store)           # number of distinct texts
store.text           # StringsMmap of the texts
store.content_hash   # numpy memmap of sha256 hexdigests, dtype "<U64"

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mmap_ninja_dataframe-0.9.0.tar.gz (19.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mmap_ninja_dataframe-0.9.0-py3-none-any.whl (15.6 kB view details)

Uploaded Python 3

File details

Details for the file mmap_ninja_dataframe-0.9.0.tar.gz.

File metadata

  • Download URL: mmap_ninja_dataframe-0.9.0.tar.gz
  • Upload date:
  • Size: 19.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for mmap_ninja_dataframe-0.9.0.tar.gz
Algorithm Hash digest
SHA256 ed41e01c886c03ab0945c0906f42516807aa4aa7c7d7d8fa83ce94dfe17a6acc
MD5 cb9874232887a378c1465a2f07cd39a2
BLAKE2b-256 dbcb77480c61481e9f9c653ff63a1dcc8b850ea70bd7642f748d9ecccfd4f6af

See more details on using hashes here.

File details

Details for the file mmap_ninja_dataframe-0.9.0-py3-none-any.whl.

File metadata

File hashes

Hashes for mmap_ninja_dataframe-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bc4b30752f196065b12cc33266516bd9f7a8978d791f56781a31b6a04fbce4ae
MD5 34f4f0844b8cc30585b418fe94f5797b
BLAKE2b-256 ea29349a2afb3c557c40c9e4bea161599113c3921e3399aabfd27110dd856611

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page