在数据科学项目中引用文件的优雅方式
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
问题:
以这种方式引用文件是常见的做法吗? settings.DATA_DIR
有点丑
settings.py
应该如何使用?它应该放在这个目录中吗?我在 .samr/settings.py
下的这个 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__.py
、config.py
、settings.py
或其他)(想象一下将 fs 换成 blobstore 或其他)。 setup.py 通常在顶层 my_project/setup.py
和 my_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。允许访问 raw
、interim
和 processed
文件夹中的特定数据。
这可以是一个简单的函数,通过文件夹中的文件名返回完整路径,例如:
def interim(filename):
"""Return path for *filename* in 'data/interim folder'."""
return str(ROOT / 'data' / 'interim' / filename)
在我的项目中,我有 interim
和 processed
目录的年月子文件夹,我按年、月和有时频率来处理数据。对于这个数据结构,我有
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')
助手代码 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
。
最近几天我一直在学习如何构建数据科学项目以使其简单、可重用和 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
问题:
以这种方式引用文件是常见的做法吗?
settings.DATA_DIR
有点丑settings.py
应该如何使用?它应该放在这个目录中吗?我在.samr/settings.py
下的这个 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__.py
、config.py
、settings.py
或其他)(想象一下将 fs 换成 blobstore 或其他)。 setup.py 通常在顶层 my_project/setup.py
和 my_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。允许访问 raw
、interim
和 processed
文件夹中的特定数据。
这可以是一个简单的函数,通过文件夹中的文件名返回完整路径,例如:
def interim(filename):
"""Return path for *filename* in 'data/interim folder'."""
return str(ROOT / 'data' / 'interim' / filename)
在我的项目中,我有 interim
和 processed
目录的年月子文件夹,我按年、月和有时频率来处理数据。对于这个数据结构,我有
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')
助手代码 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
。