优雅的方式来引用数据科学项目中的文件

dyl*_*fan 9 python directory-structure setuptools python-3.x

最近几天我花了很多时间学习如何构建数据科学项目,以保持简单,可重用和pythonic.坚持我已经创建的这个指南my_project.你可以在下面看到它的结构.

??? README.md          
??? data
?   ??? processed          <-- data files
?   ??? raw                            
??? notebooks  
|   ??? notebook_1                             
??? setup.py              
|
??? settings.py            <-- settings file   
??? src                
    ??? __init__.py    
    ?
    ??? data           
        ??? get_data.py    <-- script  
Run Code Online (Sandbox Code Playgroud)

我定义了一个从中加载数据的函数.data/processed.我想在其他脚本中以及位于.notebooks中的jupyter笔记本中使用此函数.

def data_sample(code=None):
    df = pd.read_parquet('../../data/processed/my_data')
    if not code:
        code = random.choice(df.code.unique())
    df = df[df.code == code].sort_values('Date')
    return df
Run Code Online (Sandbox Code Playgroud)

显然,除非我直接在定义它的脚本中运行它,否则此函数无法在任何地方运行.我的想法是创建settings.py我声明的地方:

from os.path import join, dirname

DATA_DIR = join(dirname(__file__), 'data', 'processed')
Run Code Online (Sandbox Code Playgroud)

所以现在我可以写:

from my_project import settings
import os

def data_sample(code=None):
    file_path = os.path.join(settings.DATA_DIR, 'my_data')
    df = pd.read_parquet(file_path)
    if not code:
        code = random.choice(df.code.unique())
    df = df[df.code == code].sort_values('Date')
    return df
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 这种常见做法是以这种方式引用文件吗?settings.DATA_DIR看起来有点难看.

  2. 这根本settings.py应该怎么用?它应该放在这个目录中吗?我曾在不同的点在此看到它的回购.samr/settings.py

我知道可能没有"一个正确的答案",我只是试图找到处理这些事情的逻辑,优雅的方式.

Evg*_*eny 2

我正在维护一个基于 DataDriven Cookiecutter 的经济数据项目,我认为这是一个很棒的模板。

对我来说,将数据文件夹和代码分开似乎是一个优势,允许将您的工作视为定向转换流(“ DAG”),从不可变的初始数据开始,一直到中期和最终结果。

最初,我回顾了pkg_resources,但拒绝使用它(语法很长并且缺乏对创建包的理解),而是使用自己的在目录中导航的辅助函数/类。

本质上,助手做了两件事

1. 将项目根文件夹和其他一些路径保留在常量中:

# shorter version 
ROOT = Path(__file__).parents[3]

# longer version
def find_repo_root():
    """Returns root folder for repository.
    Current file is assumed to be:
        <repo_root>/src/kep/helper/<this file>.py
    """
    levels_up = 3
    return Path(__file__).parents[levels_up]

ROOT = find_repo_root()
DATA_FOLDER = ROOT / 'data' 
UNPACK_RAR_EXE = str(ROOT / 'bin' / 'UnRAR.exe')
XL_PATH = str(ROOT / 'output' / 'kep.xlsx')
Run Code Online (Sandbox Code Playgroud)

这与您对 所做的类似DATA_DIR。一个可能的弱点是,这里我手动硬编码帮助程序文件相对于项目根目录的相对位置。如果辅助文件位置发生移动,则需要进行调整。但是,嘿,这与Django中的做法相同。

2. 允许访问rawinterimprocessed文件夹中的特定数据。

这可以是一个简单的函数,按文件夹中的文件名返回完整路径,例如:

def interim(filename):
    """Return path for *filename* in 'data/interim folder'."""
    return str(ROOT / 'data' / 'interim' / filename)
Run Code Online (Sandbox Code Playgroud)

在我的项目中,我有年月子文件夹interimprocessed目录,并且我按年、月和有时频率来处理数据。对于这个数据结构,我有 InterimCSV提供ProcessedCSV参考特定路径的类,例如:

from . helper import ProcessedCSV, InterimCSV
 # somewhere in code
 csv_text = InterimCSV(self.year, self.month).text()
 # later in code
 path = ProcessedCSV(2018,4).path(freq='q')
Run Code Online (Sandbox Code Playgroud)

助手的代码在这里。此外,如果子文件夹不存在,这些类会创建子文件夹(我希望在临时目录中进行单元测试),并且有一些方法可以检查文件是否存在并读取其内容。

在您的示例中,您可以轻松地将根目录固定在 中setting.py,但我认为您可以在抽象数据方面向前迈进一步。

目前data_sample()混合了文件访问和数据转换,这不是一个好兆头,并且还使用全局名称,这是函数的另一个坏兆头。我建议您可以考虑以下几点:

# keep this in setting.py
def processed(filename):
   return os.path.join(DATA_DIR, filename)

# this works on a dataframe - your argument is a dataframe,
# and you return a dataframe
def transform_sample(df: pd.DataFrame, code=None) -> pd.DataFrame:
    # FIXME: what is `code`?
    if not code:
        code = random.choice(df.code.unique())
    return df[df.code == code].sort_values('Date')

# make a small but elegant pipeline of data transfomation
file_path = processed('my_data')
df0 = pd.read_parquet(file_path)
df = transform_sample(df0)
Run Code Online (Sandbox Code Playgroud)