Skip to main content

Succinct small scale data manipulation

Project description

Martens

https://img.shields.io/pypi/v/martens.svg Documentation Status

Succinct small scale data manipulation

Usage

To use Martens in a project:

import martens

The package is available freely on pypi under MIT licence.

About

Martens is a python package for data manipulation in python. It is designed for data that is too small,for example, to worry about uploading into a cloud data warehouse for ease of processing but which is still useful to you. The kind of data that was probably passed to you in a spreadsheet or csv file which needs to be transformed quickly into what you want.

The primary aim of Martens is to enable data manipulation code that is:

  • Flexible

  • Succinct

  • Easily Readable and maintainable

  • Lightweight

And finally, reasonably performant. That is to say, the intent and philosophy is not to rely on libraries like numpy which may boost performance compared to base python. Rather, martens fits neatly around concepts from core python. This comes with benefits to flexibility and a minimal build profile.

The design is heavily inspired by dplyr from the R universe.

Example code

Importing data is simple:

source_data = martens.SourceFile(file_path=file_path).dataset

Generally speaking, martens will infer file type from the file extension provided but you can specify a file type to override it. Access the underlying dataframe with dataset property.

Dataframes

A martens dataframe is really just a dict of equal length lists and string keys. Any column of the dataframe can be accessed as follows and the result is always a list:

source_data['age']

There’s no such thing as a dataframe index in martens, they are not at all useful for the type of data that martens is designed to parse. But, we can quickly add a standard, integer based column to use as an id in later steps:

source_data.with_id('person_id')

Filtering with functions

Filtering is best done with functions or lambdas (but doesn’t have to be):

source_data.dataset.filter(lambda gender: gender == 'Male')

The key innovation of martens is that argument names of a function are used within the dataframe. These functions will then operate on data within the columns with corresponding names to it’s own arguments. That is, argument names of your functions are important and determine how that function will interact with the dataframe. This allows for succinct, readable and flexible code. For example, you can use any function to filter so long as its argument names correspond to existing columns in the dataframe and the function returns something that is ultimately able to be resolved to either true or false.

Mutate and apply

Similarly we can quickly create new columns on the fly using data from existing functions:

source_data.mutate(lambda age: 365*age,'age_in_days')

Again, there is significant flexibility here. Any arbitrary function with any arbitrary return value will do, as long as all of it’s arguments can be resolved using existing columns of the dataframe.

If you just want the output without adding to the dataframe, use apply:

source_data.apply(lambda age: 7*age)

Stack, stretch and squish

Sometimes, we don’t want to simply create a new column with the required features. If the output of your function resolves to a list, you can choose to stack the output vertically. This will produce a new dataframe with additional rows and the existing columns expanded (repeated):

source_data.mutate_stack(lambda age: list(range(age)),)

We might instead want to create multiple new columns simultaneously:

source_data.mutate_stretch(some_function_returning_tuple_of_2,names=['A','B'])

More complex code

If you are using martens the way it was intended, your code will tend to have large blocks of three plus lines of code with each new operation just being a method of the dataframe from the the previous line. That is, chaining commands is common:

def solve()
    data = mt.Dataset({'line': [x for x in data_input.split('\n')]})
    num_match = lambda line: [match for match in re.finditer(r'\b\d+\b', line)]
    num_matches = data.with_id('num_line_no') \
        .mutate_stack(num_match, 'match').with_id('num_id') \
        .mutate(lambda match: int(match.group()), name='num_match') \
        .mutate(lambda match: match.start(), name='num_start') \
        .mutate(lambda match: match.end(), name='num_end')
    chr_match = lambda line: [m.start() for m in re.finditer(r'[^.0-9]', line)]
    chr_matches = data.with_id('chr_line_no') \
        .mutate_stack(chr_match, 'chr_match') \
        .with_id('chr_id').select(['chr_line_no', 'chr_match', 'chr_id'])
    all_matches = num_matches.merge(chr_matches) \
        .filter(lambda chr_line_no, num_line_no: abs(chr_line_no - num_line_no) <= 1) \
        .filter(lambda chr_match, num_start, num_end: num_start - 1 <= chr_match <= num_end)
    gear_match = all_matches.group_by(['chr_id'], other_cols=['num_id', 'num_match']) \
        .mutate(lambda num_id: len(num_id), 'num_count') \
        .filter(lambda num_count: num_count >= 2) \
        .mutate(lambda num_match: prod(num_match), 'gear_ratio')
    return {
        'part one': sum(all_matches.unique_by(['num_id', 'num_match'])['num_match']),
        'part two': sum(gear_match['gear_ratio'])
    }

Extensibility

A martens dataframe can often be used in place of a pandas dataframe or similar in another package. For example in plotly

import plotly.express as px
px.bar(dataframe,x='column1',y='column2')

What’s next

This is just the beginning of this project, I hope it is useful to someone, somewhere. There are many, many feature and speed improvements that I would like to implement. Of course, feedback is welcome, raise an issue or otherwise get in touch and I’ll do my best to respond.

History

0.2.1 (2024-01-11)

  • First release featured on PyPI.

Project details


Download files

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

Source Distribution

martens-0.3.1.tar.gz (27.0 kB view details)

Uploaded Source

Built Distribution

martens-0.3.1-py2.py3-none-any.whl (11.2 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file martens-0.3.1.tar.gz.

File metadata

  • Download URL: martens-0.3.1.tar.gz
  • Upload date:
  • Size: 27.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.9.19

File hashes

Hashes for martens-0.3.1.tar.gz
Algorithm Hash digest
SHA256 5797e6db1de126a6b9cb23d6a1d763841e9f6001d4b7712c5e5c9d75979486e2
MD5 53c0e44ea46ddfcc11166c618f8cb78b
BLAKE2b-256 2e488d3d2da7c36bce36e84889cb52d0c3f88121fe4ea12a799ee43beded63ea

See more details on using hashes here.

File details

Details for the file martens-0.3.1-py2.py3-none-any.whl.

File metadata

  • Download URL: martens-0.3.1-py2.py3-none-any.whl
  • Upload date:
  • Size: 11.2 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.9.19

File hashes

Hashes for martens-0.3.1-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 7fd9252baae3da980c555ecb88f8a3147624a6c4e3ee6432be566d7c0e217249
MD5 30751ec6540df7bb4c43ed64b0fefcd9
BLAKE2b-256 d6de0a4eff51e0ff8e38a789235cff6e8cf021dceb281d79269a39ba011e5cd9

See more details on using hashes here.

Supported by

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