优雅地引用数据科学项目中的文件

11

最近几天,我花费时间学习如何构建数据科学项目以使其简单、可重复和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  

我定义了一个函数,从.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

很明显,除非我直接在定义它的脚本中运行该函数,否则它将无法在任何地方正常工作。 我的想法是创建一个名为settings.py的文件,在其中声明:

from os.path import join, dirname

DATA_DIR = join(dirname(__file__), 'data', 'processed')

现在我可以写:

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

问题:

  1. 这种方式引用文件是常见的吗?settings.DATA_DIR看起来有些难看。

  2. settings.py应该如何使用?它应该放在这个目录下吗?我在这个repo的不同位置看到过它,位于.samr/settings.py下。

我明白可能没有“一个正确答案”,我只是试图找到处理这些事情的逻辑、优雅的方法。


1
请不要将评论放在问题帖子中。Stack Overflow不像论坛;我们想要建立问题及其答案,以便未来的访问者可以快速查看是否有相同的问题,并且答案也适用于他们的情况。他们不会关心过程最终如何得出这些答案;有关答案的对话应保留在评论中。 - Martijn Pieters
明白,我以后会坚持这个做法来回答问题。 - dylan_fan
1
我发现以下关于打包和项目布局的额外资源非常有用: https://realpython.com/python-application-layouts/ https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure - dylan_fan
4个回答

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')

这类似于您在DATA_DIR中所做的操作。可能的一个弱点是,我在这里手动硬编码了助手文件相对于项目根目录的位置。如果助手文件位置发生变化,需要进行调整。但是,嘿,这与Django的做法是一样的。
2. 允许访问rawinterimprocessed文件夹中的特定数据。
这可以是一个简单的函数,通过文件名返回一个文件夹中的完整路径,例如:
def interim(filename):
    """Return path for *filename* in 'data/interim folder'."""
    return str(ROOT / 'data' / 'interim' / filename)

在我的项目中,我有按年月分类的interimprocessed文件夹,并且我通过年份、月份和频率来访问数据。针对这种数据结构,我有InterimCSVProcessedCSV类,可以提供特定路径的引用,例如:
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')

助手代码 在这里。此外,如果不存在(我想要这个来临时目录中进行单元测试),类会创建子文件夹,并有用于检查文件是否存在以及读取其内容的方法。
在您的示例中,可以轻松地在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)

1
只要您没有提交大量数据并且清楚地区分了受控的外部世界快照和自己派生的数据(代码+原始数据)==状态,就可以使用类似追加式的原始数据,并考虑符号链接步骤,例如raw/interesting_source/2018.csv.gz -> raw_appendonly/interesting_source/2018.csv.gz.20180401T12:34:01或类似模式来建立“使用最新”的输入结构。尝试清晰地分离配置设置(my_project/__init__.pyconfig.pysettings.py或其他可能需要根据环境进行更改的设置)。setup.py通常位于顶层my_project/setup.py,与可运行内容相关的任何内容(不包括文档、示例等)在my_project/my_project中。在一个地方(config.py)定义一个_mydir = os.path.dirname(os.path.realpath(__file__)),并依靠它避免重构痛苦。

0

不,如果您使用Django,则使用settings.py仅是常见做法。至于以这种方式引用数据目录,这取决于您是否希望用户能够更改此值。您设置的更改该值的方式需要编辑settings.py文件。如果您希望用户具有默认值,但也可以在使用函数时轻松更改它,请在def data_sample(...,datadir = filepath)中创建基本路径值并将其设置为默认值。


0

您可以使用open()打开文件,并将其保存在变量中,然后在需要引用该文件的任何地方继续使用该变量。

with open('Test.txt','r') as f:

或者

f=open('Test.txt','r')

使用f来引用文件。 如果您希望文件既可读又可写,可以使用r+代替r


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接