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

Elegant way to refer to files in data science project

最近几天我一直在学习如何构建数据科学项目以使其简单、可重用和 pythonic。坚持 this guideline 我创造了 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 应该如何使用?它应该放在这个目录中吗?我在 .samr/settings.py

  3. 下的这个 repo 的不同位置看到过它

我知道可能没有 'one right answer',我只是试图找到处理这些事情的合乎逻辑、优雅的方式。

不,只有在使用 Django 时才会使用 settings.py。至于以这种方式引用数据目录,这取决于您是否希望用户能够更改此值。设置更改值的方式需要编辑 settings.py 文件。如果您希望用户拥有默认值,但也能够在他们使用您的函数时轻松更改它,只需内联创建基本路径值并将其设为 def data_sample(..., datadir=filepath ):.

只要您不提交大量数据,并且清楚不受控制的外部世界的快照与您自己的派生数据(代码 + raw)== 状态之间的区别。有时使用 append-only-ish raw 并考虑像 raw/interesting_source/2018.csv.gz -> raw_appendonly/interesting_source/2018.csv.gz.20180401T12:34:01 这样的符号链接步骤或一些类似的模式来建立 "use latest" 输入结构是有用的。尝试清楚地分隔可能需要根据环境更改的配置设置(my_project/__init__.pyconfig.pysettings.py 或其他)(想象一下将 fs 换成 blobstore 或其他)。 setup.py 通常在顶层 my_project/setup.pymy_project/my_project 中与可运行的东西(不是文档,示例不确定)相关的任何东西。在一个地方定义一个 _mydir = os.path.dirname(os.path.realpath(__file__)) (config.py) 并依靠它来避免重构的痛苦。

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

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

最初,我查看了 pkg_resources,但拒绝使用它(冗长的语法和缺乏对创建包的理解),转而使用自己的助手 functions/classes 在目录中导航。

基本上,帮手做两件事

1。在 constansts 中保留项目根文件夹和其他一些路径:

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

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

在您的示例中,您可以轻松地将根目录固定在 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)

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

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

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

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