Your sample solution using dummy-data worked great!
Since we ran out of space above, to summarize:
Accept data from a samples.txt files via:
# getting the samples information (names, path to r1 & r2) from samples.txt
samples_information = pd.read_csv("samples.txt", sep='\t', index_col=False)
# get a list of the sample names
sample_names = list(samples_information['sample'])
sample_locations = list(samples_information['location'])
samples_dict = dict(zip(sample_names, sample_locations))
# get number of samples
len_samples = len(sample_names)
It's best to use a function as input for each rule(s). For example, for QC and trimming you would use the following as they are paired-end:
def getHome(sample):
return(list(os.path.join(samples_dict[sample],"{0}_{1}.fastq.gz".format(sample,pair)) for pair in ['R1','R2']))
The length of the input (how many samples are being processed) is determined via len_samples = len(sample_names) and for trimming it would look like:
trim_dir = os.path.join(dirs_dict["TRIM_DIR"],config["TRIM_TOOL"])
trims_locations = [trim_dir] * len_samples
trims_dict = dict(zip(sample_names, trims_locations))
def getTrims(sample):
return(list(os.path.join(trims_dict[sample],"{0}_{1}_trim_paired.fq.gz".format(sample,pair)) for pair in ['R1','R2']))
I've done the following for post-alignment (or cases where you only have a single file input) but I'm not sure if it's the best way to do it (nonetheless it works):
align_dir = os.path.join(dirs_dict["ALIGN_DIR"],config["ALIGN_TOOL"])
aligns_locations = [align_dir] * len_samples
aligns_dict = dict(zip(sample_names, aligns_locations))
def getBams(sample):
return(list(os.path.join(aligns_dict[sample],"{0}.bam".format(sample,pair)) for pair in ['']))
You taught me I could use temp() in the output: of Snakemake rules that way I wouldn't have to specify them all in rule all. os.path.join() should be used instead of expand() in rules - I'm really surprised the official documentation focuses so much on expand() when IMO this is as important!
temp(os.path.join(dirs_dict["ANNOT_DIR"],config["ANNOT_TOOL"],"{sample}/intermediate-files/{sample}_sorted_dedupped_snp_varscan.tsv.del"))
And finally, the only place you should use expand() should be in the rule all.
rule all:
input:
expand('{QC_DIR}/{QC_TOOL}/before_trim/{sample}_{pair}_fastqc.{ext}', QC_DIR=dirs_dict["QC_DIR"], QC_TOOL=config["QC_TOOL"], sample=sample_names, pair=['R1', 'R2'], ext=['html', 'zip'])
Thank you so much for your week of support! Hopefully this helps others in the future as well.
zip) was just changing the input of the rule to your suggestion.Switch:
into
However, it's looking for the input in QC directory (which is the output of this rule and not the input)
If I don't need to use
expand()in target rules I won't - I think your suggestion improves code readability - however, it doesn't seem to be working...When you mention intermediate targets I assume you're referring to the
coovarrule?These are a temporary files created by
coovar- I do not necessarily require them downstream.Given what you've said in #1 & #3 (if this does work somehow), and what's in Step 6. of the Snakemake Advanced manual could I do the following to have them deleted?
zip:I can figure out how to include them as input using
expand()then but I'm not sure how to do otherwise:So to summarize, your suggestions for eliminating redundancy in the
expand()statements and usingzip()are wonderful and seem to do the trick - thank you!Making the code more readable by entirely doing away with
expand()would be excellent but I seem to be having some issues with this still.ok yes you can prepend your input with directories i.e
r1 = os.path.join(FASTQ_DIR,"{sample}_R1.fastq.gz"). that's ok i would just avoidexpandin wildcards rules.now for the zip thing you are close. you can just use
samples_setto create a lookup functiondef getHome(sample): return(os.path.join(dict(samples_set)[sample],sample)))and then set your directory in the input tomydir = lambda wildcards: getHome(wildcards.sample).Sorry but I'm still confused as to what I need to do.
Your suggestion is to use
os.path.join()instead ofexpand()in wildcards rules, correct?FASTQ_DIRhasn't be defined anywhere so I cannot just use that - I assumed you meant you need to setmydirinstead?Your
def getHome(sample)function may have contained an extra)at the end because I got an error. I've tried the following but get an error:ok there's many ways to skin a cat but this is one strategy:
def getHome(sample): return(os.path.join(dict(samples_set)[sample],"{0}_{1}.fastq.gz".format(sample,pair)) for pair in ['R1','R2'])r1 = lambda wildcards: getHome(wildcards.sample)[0],r2 = lambda wildcards: getHome(wildcards.sample)[1]I'm getting the following error:
don't use expand in wildcard rules
Do you mean in the
outputof the rule?If so does this apply to
log:andparams:as well?I have no idea how not use
expand()for this statement asQC_TOOLis defined in the config fileQC_TOOL=config["QC_TOOL"]I've even tried to simplify the
rule alland theoutputto not use expand and as you can see I'm struggling:Okay so switching to this now produces this error:
uhh test that getHome lookup function in isolation. dict(sample_set) should be a dictionary with the samples as keys and paths as values. the goal is to have a function that takes a sample and returns a list of 2 paths, one for each pair
ummm okay, so I'm not really sure how to troubleshoot/test this?
Not able to run the function
getHome()as-is so I tried breaking it down into sections. Callingdict(samples_set)gives this error so perhapssamples_setis wrong?I read somewhere that the issue could be because of feeding in a list instead of a tuple to
dict():Hmmm....okay....
okay now that I verified this works let's try on
samples_set:Omg I have no idea what's going here....
Appreciate your help figuring this out
I believe one of the errors is that
samples_setwas not defined properly. Maybe it should look like this instead?Now I can try:
So that seems to work (although I'm not sure if the order of sample/location is correct?
Not sure how to test the
getHome()function further...ok i guess you have to explicitly make sure python runs the generator (notice the
listcast)Okay so you used
sample_dicthere which I suppose is the same asdict(samples_set). I noticed I had the order for thekeywrong earlier insamples_setso I've fixed that now.The following change to the snakemake file:
But I'm getting the error:
ok what does
dict(samples_set)look like? is BC1217 a key?I think this has something to do with you mixing integers and strings in your keys (sample names). Make sure you explicitly state that
470is a string when you build the dictionary and the Snakemake target.I've spent some time searching for this but I have no idea how to do such a thing in Python (I use R).
There was this example of how to do the opposite of what you suggested.:
So I thought to myself, okay since:
I should just convert
sample_namesto"instead of'but this SO post say's you cannot so I'm confused...Could you please explain how I can do this?
Actually before we get too sidetracked...
I've removed the
470sample from thesamples.txtjust to simplify things - the error persists. Besides, the error was, and still is, pointing toBC1217.ok what i think is going on is the generator object returned by zip is getting spent without being reset, so it is empty the next time it's evaluated. just create a
dictright away and use thatsamples_dict = dict(zip(sample_names, sample_locations))Thanks Jeremy, I think there is some progress snow:
It looks like the
getHome()function is now behaving properly - it returns the path to both R1/R2.Unfortunately still getting an error when trying to run snakemake.
And the error:
i don't see you using a
samples_dictA rose by any other name
samples_set = dict(zip(sample_names, sample_locations))So does the function then need to be changed?
If so, how?
Changing to the following errors:
samples_dict[sample]Yes I had also tried that.
i'm running out of horizontal space, let's move this discussion to https://github.com/leipzig/biostars439754
Worked for me.
I've made a PR regarding how to include the directories that come before
"{sample}_R1_fastqc.html":How do I tell
"{QC_DIR}/{QC_TOOL}/{sample}_R1_fastqc.html"comes fromQC_DIR=dirs_dict["QC_DIR"], QC_TOOL=config["QC_TOOL"])?For example, if there was no dynamically generated
QC_TOOLfromconfig=["QC_TOOL"]one could simple do:os.path.join(dirs_dict["QC_DIR"] + '/' + '/' + '{sample}_R1_fastqc.html')However, I want to include information from the
config.yamlbut it doesn't accept fromos.path.join(dirs_dict["QC_DIR"] + '/' + config["QC_TOOL"] + '/' + '{sample}_R1_fastqc.html'):This is also redundant for both
{pair}and{ext}. So what's the proper, succinct, way to do this?I pushed another commit that uses what you propose
published at as a CodeOcean capsule: https://codeocean.com/capsule/4796507/tree/v1