<commit_before>'''
This module provides classes and functions that facilitate working with
recordings created by psiexperiment.

The base class of all experiments is `Recording`. Some subclasses (e.g.,
`psi.data.io.abr.ABRFile`) offer more specialized support for a particular
experiment type.
'''
import logging
log = logging.getLogger(__name__)

import functools
from pathlib import Path

import numpy as np
import pandas as pd
from scipy import signal


def get_unique_columns(df, exclude=None):
    return [c for c in df if (len(df[c].unique()) > 1) and (c not in exclude)]


class Recording:
    '''
    Wrapper around a recording created by psiexperiment

    Parameters
    ----------
    base_path : :obj:`str` or :obj:`pathlib.Path`
        Folder containing recordings

    Attributes
    ----------
    base_path : pathlib.Path
        Folder containing recordings
    carray_names : set
        List of Bcolz carrays in this recording
    ctable_names : set
        List of Bcolz ctables in this recording
    ttable_names : set
        List of CSV-formatted tables in this recording

    The `__getattr__` method is implemented to allow accessing arrays and
    tables by name. For example, if you have a ctable called `erp_metadata`:

        recording = Recording(base_path)
        erp_md = recording.erp_metadata

    When using this approach, all tables are loaded into memory and returned as
    instances of `pandas.DataFrame`. All arrays are returned as `Signal`
    instances. Signal instances do not load the data into memory until the data
    is requested.
    '''

    #: Mapping of names for CSV-formatted table to a list of columns that
    #: should be used as indices. For example:
    #:     {'tone_sens': ['channel_name', 'frequency']}
    #: This attribute is typically used by subclasses to automate handling of
    #: loading tables into DataFrames.
    _ttable_indices = {}

    def __init__(self, base_path):
        bp = Path(base_path)
        self.base_path = bp
        self._refresh_names()

    def _refresh_names(self):
        '''
        Utility function to refresh list of signals and tables

        This is primarily used by repair functions that may need to fix various
        items in the folder when the class is first created.
        '''
        bp = self.base_path
        self.carray_names = {d.parent.stem for d in bp.glob('*/meta')}
        self.ctable_names = {d.parent.parent.stem for d in bp.glob('*/*/meta')}
        self.ttable_names = {d.stem for d in bp.glob('*.csv')}

    def __getattr__(self, attr):
        if attr in self.carray_names:
            return self._load_bcolz_signal(attr)
        if attr in self.ctable_names:
            return self._load_bcolz_table(attr)
        elif attr in self.ttable_names:
            return self._load_text_table(attr)
        raise AttributeError

    def __repr__(self):
        lines = [f'Recording at {self.base_path.name} with:']
        lines.append(f'* Bcolz carrays {self.carray_names}')
        lines.append(f'* Bcolz ctables {self.ctable_names}')
        lines.append(f'* CSV tables {self.ttable_names}')
        return '\n'.join(lines)

    @functools.lru_cache()
    def _load_bcolz_signal(self, name):
        from .bcolz_tools import BcolzSignal
        return BcolzSignal(self.base_path / name)

    @functools.lru_cache()
    def _load_bcolz_table(self, name):
        from .bcolz_tools import load_ctable_as_df
        return load_ctable_as_df(self.base_path / name)

    @functools.lru_cache()
    def _load_text_table(self, name):
        import pandas as pd
        path = (self.base_path / name).with_suffix('.csv')
        if path.stat().st_size == 0:
            return pd.DataFrame()<commit_msg>Better formatting of Recording str rep<commit_after>'''
This module provides classes and functions that facilitate working with
recordings created by psiexperiment.

The base class of all experiments is `Recording`. Some subclasses (e.g.,
`psi.data.io.abr.ABRFile`) offer more specialized support for a particular
experiment type.
'''
import logging
log = logging.getLogger(__name__)

import functools
from pathlib import Path

import numpy as np
import pandas as pd
from scipy import signal


def get_unique_columns(df, exclude=None):
    return [c for c in df if (len(df[c].unique()) > 1) and (c not in exclude)]


class Recording:
    '''
    Wrapper around a recording created by psiexperiment

    Parameters
    ----------
    base_path : :obj:`str` or :obj:`pathlib.Path`
        Folder containing recordings

    Attributes
    ----------
    base_path : pathlib.Path
        Folder containing recordings
    carray_names : set
        List of Bcolz carrays in this recording
    ctable_names : set
        List of Bcolz ctables in this recording
    ttable_names : set
        List of CSV-formatted tables in this recording

    The `__getattr__` method is implemented to allow accessing arrays and
    tables by name. For example, if you have a ctable called `erp_metadata`:

        recording = Recording(base_path)
        erp_md = recording.erp_metadata

    When using this approach, all tables are loaded into memory and returned as
    instances of `pandas.DataFrame`. All arrays are returned as `Signal`
    instances. Signal instances do not load the data into memory until the data
    is requested.
    '''

    #: Mapping of names for CSV-formatted table to a list of columns that
    #: should be used as indices. For example:
    #:     {'tone_sens': ['channel_name', 'frequency']}
    #: This attribute is typically used by subclasses to automate handling of
    #: loading tables into DataFrames.
    _ttable_indices = {}

    def __init__(self, base_path):
        bp = Path(base_path)
        self.base_path = bp
        self._refresh_names()

    def _refresh_names(self):
        '''
        Utility function to refresh list of signals and tables

        This is primarily used by repair functions that may need to fix various
        items in the folder when the class is first created.
        '''
        bp = self.base_path
        self.carray_names = {d.parent.stem for d in bp.glob('*/meta')}
        self.ctable_names = {d.parent.parent.stem for d in bp.glob('*/*/meta')}
        self.ttable_names = {d.stem for d in bp.glob('*.csv')}

    def __getattr__(self, attr):
        if attr in self.carray_names:
            return self._load_bcolz_signal(attr)
        if attr in self.ctable_names:
            return self._load_bcolz_table(attr)
        elif attr in self.ttable_names:
            return self._load_text_table(attr)
        raise AttributeError

    def __repr__(self):
        lines = [f'Recording at {self.base_path.name} with:']
        if self.carray_names:
            lines.append(f'* Bcolz carrays {self.carray_names}')
        if self.ctable_names:
            lines.append(f'* Bcolz ctables {self.ctable_names}')
        if self.ttable_names:
            lines.append(f'* CSV tables {self.ttable_names}')
        return '\n'.join(lines)

    @functools.lru_cache()
    def _load_bcolz_signal(self, name):
        from .bcolz_tools import BcolzSignal
        return BcolzSignal(self.base_path / name)

    @functools.lru_cache()
    def _load_bcolz_table(self, name):
        from .bcolz_tools import load_ctable_as_df
        return load_ctable_as_df(self.base_path / name)

    @functools.lru_cache()
    def _load_text_table(self, name):
        import pandas as pd
        path = (self.base_path / name).with_suffix('.csv')
        if path.stat().st_size == 0:
            return pd.DataFrame()
{'fraction_non_alphanumeric': 0.08675016479894529, 'fraction_numerical': 0.0005273566249176005, 'mean_word_length': 2.727764127764128, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3359346', 'n_tokens_mistral': 2315, 'n_tokens_neox': 2214, 'n_words': 750}