-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutilities.py
More file actions
1595 lines (1319 loc) · 69.6 KB
/
Copy pathutilities.py
File metadata and controls
1595 lines (1319 loc) · 69.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
This is a poorly-organized collection of utilities of general, errr, utility.
Configuration: See cpblUtilities-config.py. In short, copy config-template.cfg to config.cfg and edit it.
A config.cfg file should be used to set up folders to be used by cpblUtilities. The configuration procedure is as follows:
(1) If config.cfg exists locally, it will be used.
(2) Otherwise, cpblUtilities will look for a config.cfg file in its own (the cpblUtilities repository) folder.
"""
from .cpblUtilities_config import *
import os
import re
from copy import deepcopy
import sys
import time
def debugprint(a='',b='',c='',d='',f='',g='',h='',i='',j='',k='',l='',m='',n=''):
#print 'DEBUG -- '+str([a,b,c,d,f,g,h,i,j,k,l,m,n])
pass
return
def toYearFraction(date):
""" Convert a date to a decimal number of years
It takes a datetime object, I tink. """
import datetime as dt
import time
def sinceEpoch(date): # returns seconds since epoch
return time.mktime(date.timetuple())
s = sinceEpoch
if isinstance(date,list) and len(date)==3:
date=dt.date(date[0],date[1],date[2])
year = date.year
startOfThisYear = dt.datetime(year=year, month=1, day=1)
startOfNextYear = dt.datetime(year=year+1, month=1, day=1)
yearElapsed = s(date) - s(startOfThisYear)
yearDuration = s(startOfNextYear) - s(startOfThisYear)
fraction = yearElapsed/yearDuration
return date.year + fraction
#---function to grab a web page---#000000#FFFFFF--------------------------------
def wget(url,binaryMode=0):
"""Grab a page and extract certain infos"""
import urllib
import urllib2
import re # Regular expressions
from time import sleep
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
req = urllib2.Request(url, None, headers)
htmlSource=[]
while not htmlSource:
try:
htmlSource = urllib2.urlopen(req).read()#,headers)
# sock = urllib.urlopen(url)
except: # urllib2.HTTPError, e:
print('Caught an error reading URL...HTTP?!')
htmlSource=[]
sleep(5)
#if e.code == 401:
# dlog('HTTP ERROR!!: not authorized')
#elif e.code == 404:
# dlog('HTTP ERROR!!: not found')
#elif e.code == 503:
# dlog('HTTP ERROR!!: service unavailable')
#else:
# dlog('HTTP ERROR!!: unknown error: ')
if not htmlSource:
print('Failed to open url'+url)
## sock=[]
## while not sock:
## try:
## sock = urllib2.urlopen(req)
## # sock = urllib.urlopen(url)
## except urllib2.HTTPError, e:
## sock=[]
## if e.code == 401:
## dlog('HTTP ERROR!!: not authorized')
## elif e.code == 404:
## dlog('HTTP ERROR!!: not found')
## elif e.code == 503:
## dlog('HTTP ERROR!!: service unavailable')
## else:
## dlog('HTTP ERROR!!: unknown error: ')
## if not sock:
## dlog('Failed to open url'+url)
## htmlSource = sock.read()
#sock.close()
if not binaryMode:
htmlSource=re.sub("\n","",str(htmlSource)).replace('\r','') # Remove newlines and ^M's
# Remove all newlines and all tabs from html. Later I use tabs for CSV
return(str(htmlSource).expandtabs())
else:
return(htmlSource)
###########################################################################################
###
def doSystem(acommand,verbose=False,bg=None):
###
#######################################################################################
"""
2011/2: Added some facility for background launching.
"""
if bg=='ifserver':
if 'apollo' in os.uname()[1]:
bg=True
else:
bg=False
if bg in [None,False]:
debugprint( ' Calling system: %s'%acommand,)
if verbose:
print( ' Calling system: %s'%acommand,)
for oneline in acommand.split('\n'):
os.system(oneline)
debugprint( ' ... Done system call')
return
if bg is True:
if verbose:
print( ' Calling system: %s in background'%acommand,)
import tempfile
fn=tempfile.NamedTemporaryFile('w',delete=False)
fn.write(acommand+'\n')
os.system('nohup nice /bin/bash '+fn.name+' &')
#import subprocess
#subprocess.Popen(["nohup", "python", "test.py"])
#os.system()
####################################################################################
# Aug 2010: You may still consider using shelve instead, but I've just simplifed this so that it does work now.
# See comments for what's missing.
####################################################################################
def dictToTsv(dicts,tsvFile,snan='',skipCheckStructure=False):
"""
Assumptions:
- all elements are dicts with identical structure (!) [May 2011: No, this is only relied upon if skipCheckStructure is True]
- NaN's should be stored as empty entries. Maybe this causes trouble for the first row?
Problems:
- does not yet insert a second row containing format information.
"""
outfile=open(tsvFile,'w')
keys=dicts[0].keys()
from pylab import flatten
if not skipCheckStructure: # Find superset of keys
keys=uniqueInOrder([xx for xx in flatten([dd.keys() for dd in dicts])])
else: # Or rely on every dict being identical.
keys=dd[0].keys()
if ''=='Create second line of file: indicates format types':
fieldTypes=[]
for k in keys:
if isinstance(dicts[0][k],float):
fieldTypes+=r'%f'
elif isinstance(dicts[0][k],int):
fieldTypes+=r'%d'
else:
fieldTypes+=r'%s'
outfile.write(''.join(fieldTypes)+'\n')
outfile.write('\t'.join(keys)+'\n')
if ''=='Create second line of file: indicates format types': # Right now this would NOT have the feature of dealing with NaNs
for v in dicts:
outfile.write(''.join(fieldTypes).replace(r'%','\t' r'%')[1:]%([v[k] for k in keys])+'\n')
else:
for v in dicts:
row=[]
for k in keys:
row+=[str(v.get(k,''))]
if row[-1]=='nan' and isinstance(v[k],float) and not isfinite(v[k]):
row[-1]=snan
# "nan" should never be written...
#assert not isinstance(v[k],float) or isfinite(v[k]) or row[-1]==snan
outfile.write('\t'.join(row) + '\n')
assert 'countryLong' not in tsvFile
###outfile.write('\t'.join([str(v.get(k,'')) for k in keys])+'\n')
"""
def table2tsv(filename,table):#,utf8=False):
fout=open( filename,'wt')
mk=sorted(table[0].keys())
fout.write(('\t'.join([k for k in mk])+'\n'+\
'\n'.join(['\t'.join([nm.get(k,'') for k in mk]) for nm in table])+'\n').encode('utf8'))
fout.close()
"""
return(outfile.close())
# Convert a matrix (ie list of lists) into a list of dicts, given a list of keys for the columns. This is standard for csv/CSV files. It's so short that I'm not making it into a fucntion:
#def csvToDicts(matrix,keys):
# listOfDicts=[dict(keys,row) for row in matrix]
#
# Okay: I changed my mind: Here is a function to read a tab-CSV, convert to dicts, and if asked even convert numbers to numbers.
#
# Read entire csv file into list of lines, each of which is a list of cells:
#
# 2007Aug: it seems not to convert to a dict by keys yet. This is one more line. If the key is "idnum", dates=dict([(RR['idnum'],RR) for RR in dateslist]), where dates list is returned from this fucntion
##############################################################################
##############################################################################
#
def tsvToDict(filename,keyRow=0,dataRow=1,formatRow=None,vectors=False,replaceHeaders=[],headerFormats=[],splitBy='\t',isNaN=['','no data/no data','no data','.','*','NaN'],NaN=None,utf8=True,treeKeys=None,sort=False,allowShortRows=False,singletLeaves=False):#[['',['','']]]): # Row counting starts at 0 # Needs a "keepCols"?
##########################################################################
##########################################################################
"""
This is much more fully-featured than "importSpreadsheet", which needs integrating with this...
Read a CPBL-style tab-separated-value (_tsv/csv/CSV) file into a list of dicts:
keyRow can be either a row number (0 based) or a list of column header strings.
Format row could be any of: 2 or ['f','d','s'] or '%f%d%s'
i.e. the second row of the file could specify a format string, or ..etc.
header Formats is a zip-style list of headers and corresponding formats, e.g. [('fieldone','s'),('field8','d')]
9 Aug 2006: The tsv program has grown in features. It also now assumes the file is in UTF-8 format... This is what I guess I output from OpenOffice. That means that any high characters are stored with a high prefix; typically they are thus two bytes long rather than one. This is sometimes visible in emacs or etc.
Sep2007: I've added a feature to capture Macintosh format files and load them in all at once, to fix them, rather than line by line. Oh, don't be silly: I read the whole file anyway.
Hours of effort to make this function work with troublesome cases (final blank line, extra key fields, etc)
# Hm. It looks here like the formats for replaceHeaders are: [[newname,oldname],[anothernewname,anotheroldname]] or [[newname,[oldname1,oldname2]]] or a dict: {'oldname':'newname','anotheroldname':'anothernewname'}
Sept 2008: if treeKeys is given, then the array of dicts will be shuffled to a single dict, with the dicts indexed by property (field) treeKeys.
Remember! You don't necessarily want to use this! It doesn't preserve order?!
July 2010: Added allowShortRows: so don't barf if some final elements are mising.
May 2012: Should I add the option of another row which gives descriptions of keys, ie variable labels? This doesn't fit into the Dict, but it could go into a secodn dict (or a cpbl codebook format).
Jan 2013: I will often be ussing pandas from now on, though this still is a useful.
"""
assert not singletLeaves or treeKeys
def verboseprint(ss,a='',b='',c='',d='',e='',f=''):
return()
#isNaN=['','no data/no data','no data','..','*','(dropped)']
verboseprint ('tsvToDict: Reading ',filename,'...',)
sys.stdout.flush()
# Partly-friendly filename handling:
if not filename.endswith('.tsv') and not filename.endswith('.csv') and not filename.endswith('.txt') and not os.path.splitext(filename)[1]:
filename+='.tsv'
# Check bloody file format
testline=open(filename,'rt').readline()
if '\r' in testline and '\n' not in testline:
verboseprint( 'Assuming this is a MAC text format file (sigh...) and thus splitting by \\r, using lots of memory...')
#if utf8:
# cells=[[c.strip('" \n\r').strip() for c in line.decode('utf-8').split(splitBy)] for line in re.split('\n',open(filename,'rt').read().replace('\r\n','\n').replace('\r','\n') )]
#else:
# cells=[[c.strip('" \n\r').strip() for c in line.split(splitBy)] for line in re.split('\r|\n',open(filename,'rt').read())]
xreadlines=re.split('\n',open(filename,'rt').read().replace('\r\n','\n').replace('\r','\n'))
while not xreadlines[-1]:
verboseprint ('Ignoring last line: "%s"'%xreadlines[-1])
xreadlines.pop(len(xreadlines)-1)
else:
xreadlines=open(filename,'rt').xreadlines()
# Ahh. don't strip the line before splitting in following! This removes final blank fields.
if utf8:
cells = [[c.strip('" \n').strip() for c in line.decode('utf-8').split(splitBy)] for line in xreadlines]
else:
cells = [[c.strip('" \n').strip() for c in line.split(splitBy)] for line in xreadlines]
#Kluge to deal with DOS format files? ie with \r (ie old Macintosh format!!) rather than \n (unix) or \r\n (DOS):
#print len(cells),len(cells[0])
#print cells
# if len(cells)==1 and '\r' in cells[0]:
#if len(cells)==1 and len(cells[0])==1 and '\r' in cells[0][0]:
# print 'Assuming this is a DOS text format file (sigh...) and thus splitting by \\r...([0][0] case!)'
# if utf8:
# cells=[[c.strip('" \n\r').strip() for c in line.decode('utf-8').split(splitBy)] for line in re.split('\r',cells[0][0])]
# else:
# cells=[[c.strip('" \n\r').strip() for c in line.split(splitBy)] for line in re.split('\r',cells[0][0])]
# Make sure there are enough lines in thefile to provide data!
if dataRow>=len(cells):
print ' Warning! data file %s does not have any data'%filename
return({})
#fieldDescriptions=cells[1]
#fieldNames=cells[2]
if isinstance(keyRow,list):
keys=keyRow
elif isinstance(keyRow,int):
keys=cells[keyRow]
else:
brokenTypeOfInputParam
#print cells[keyRow]
#print cells[dataRow]
# Possibly replace some of the key names. This is crude, but: ignore spaces when replacing:
if replaceHeaders:
# Start off with the Identity map:
#headerLookup={}
# Nov 2009: this very strange line was here. Why lower()??? And why not just use ".get()" rather than have defaults?
headerLookup=dict([(hni.lower().replace(' ',''),hni.lower().replace(' ','')) for hni in keys])
headerLookup={} # Just use .get(), below.
#print headerLookup
assert isinstance(replaceHeaders,dict) or isinstance(replaceHeaders,list)
if isinstance(replaceHeaders,dict):
headerLookup.update(replaceHeaders)
else:
# Nov 2009: What the hell is the below? Why the lower()??? Above is if dict form passed; below is list
for hn in replaceHeaders: # Build the look up table
if not isinstance(hn[1],list):
hn[1]=[hn[1]]
for hni in hn[1]:
headerLookup[hni.lower().replace(' ','')]=hn[0]
#print headerLookup
# Replace all keys with value from lookup table:
#keys=[headerLookup[h.lower().replace(' ','')] or h for h in keys if headerLookup.has_key(h.lower().replace(' ','')) and h]
# Nov 2009: this very strange line was here. Why lower()???
#keys=[headerLookup[h.lower().replace(' ','')] or h for h in keys if h]
keys=[headerLookup.get(h,headerLookup.get(h.lower(),headerLookup.get(h.lower().replace(' ',''),h))) or h for h in keys if h]
# if formatRow not passed but headerFormats was, create formatRow
if headerFormats and not formatRow:
if isinstance(headerFormats,basestring):
headerFormats=dict([[kk,headerFormats] for kk in keys])
if isinstance(headerFormats,list):
headerFormats=dict(zip(keys,headerFormats))
dFormats=dict(headerFormats)
formatRow=[dFormats.setdefault(k,'%s') for k in keys]
#formatRow=[dict(headerFormats).setdefault(k,'%s') for k in keys]
# Convert numeric values
if formatRow != None:
verboseprint( ' tsvToDict: Converting numbers...',)
sys.stdout.flush()
if isinstance(formatRow,int):
fieldTypes=cells[formatRow]
elif isinstance(formatRow,basestring):
fieldTypes=formatRow.split('%')[1:]
elif isinstance(formatRow,list):
fieldTypes=formatRow
#if len(fieldTypes)==1:
# fieldTypes=fieldTypes[0].split('%')[1:]
for row in cells[dataRow:]:
for ic in range(len(row)):
if 'f' in fieldTypes[ic] or 'd' in fieldTypes[ic]:
if row[ic] in isNaN:
row[ic]=NaN
else:
if 'f' in fieldTypes[ic]:
row[ic]=float(row[ic])
elif 'd' in fieldTypes[ic]:
if '.' in row[ic]:
row[ic]=int(float(row[ic]))
else:
row[ic]=int(row[ic])
# Sort the data (?!)
if sort:
cells[dataRow:]=sorted(cells[dataRow:])
# Convert matrix format into vectors or list of dictionaries:
# 2010 July (really? This hasn't been done before?!): dealing with possibility of short (incomplete) data rows.
if vectors:
verboseprint( ' tsvToDict: Returning dictionary of vectors...')
tsv={}
if allowShortRows:
for ik in range(len(keys)):
if keys[ik]:
def checkget(aa,ii,deff):
if len(aa)>ii:
return(aa[ii])
else:
return(deff)
# '' below should be facultative '' or NaN
tsv[keys[ik]]=[checkget(cells[dataRow+j],ik,{True:'',False:NaN}[fieldTypes[ic]=='s']) for j in range(len(cells[dataRow:]))]
else:
for ik in range(len(keys)):
if keys[ik]:
tsv[keys[ik]]=[cells[dataRow+j][ik] for j in range(len(cells[dataRow:]))]
else:
verboseprint( ' tsvToDict: Returning list of dicts...')
# Assume for now that keys are in English.. (str())
# Oh dear, above is a bad idea: sometimes keys are numeric! So don't recast:
# tsv=[ dict([(str(keys[i]),row[i]) for i in range(len(keys))]) for row in cells[dataRow:] ]
lens=[len(row) for row in cells[dataRow:]]
if min(lens)==max(lens) and len(keys)>min(lens):
if any([kk not in [''] for kk in keys[min(lens):]]): # ie ignore it if they're just blanks / extra tabs.
print(' Ignoring some keys due to excessive number of them (ie there are more headers than data columns?): %s'%filename, ' Extra headers are: ',keys[min(lens):])
keys=keys[0:min(lens)]
if min(lens)<len(keys):
print ' Error / warning: input file has inconsistent number of fields: there are some bad rows!: %s\n'%filename
#print lens
#print cells[dataRow:]
for irow in range(len(cells)-1,dataRow-1,-1): #Must loop backwards!
if len(cells[irow])<len(keys):
if allowShortRows:
cells[irow]+=['' for kk in range(len(keys)-len(cells[irow]))]
else:
print ' DELETING A ROW!: len=%d key length = %d min/max data length =%d/%d'%(len(cells[irow]),len(keys),min(lens),max(lens))
print cells.pop(irow) # Deletes that line!!
tsv=[ dict([(keys[i],row[i]) for i in range(len(keys))]) for row in cells[dataRow:] ]
if treeKeys: # Switch the array of dicts to a dict, with key treeKeys.
import dictTrees
tsv= dictTrees.dictTree(tsv,treeKeys)#dict([[x,[cii for cii in tsv if cii[treeKeys]==x][0]] for x in tsv if x[treeKeys]])
if singletLeaves:
tsv=tsv.singletLeavesAsDicts()
#for kk in tsv:
# if len(tsv[kk])==1:
# tsv[kk]=tsv[kk][0]
return(tsv)#,cells[0:dataRow]) # 21 Sept 2006: I took this second return argument out.
##############################################################################
##############################################################################
#
def popWordRE(sfrom,regexps,partword=False):
##########################################################################
##########################################################################
""" Updated version of below...
regexps is a *list* of strings.
If the search-for string starts and ends with paren's, don't enforce word boundaries in the search, since the parens do not make word boundaries...
"""
##import unicodedata
outlist=[]
for ss in regexps:#[s.lower() for s in regexps]:
""" Replace/Match word, possibly at end of string
If search-for string has unicode, don't force it to be its own word! (re deficiency)
Weirdness here: if the search-for string is short, let's make sure it's a word by itself:
Without re.unicode \b doesn't think high unicode chars can be part of a word
"""
# This line is a horrid kludge!!
if partword or ss.startswith(r'\.') or ss.endswith(r'\.') or (ss.startswith(r'\(') and ss.endswith(r'\)')) or (ss.startswith(r'\[') and ss.endswith(r'\]')):
reg=ss
else:
reg=r'\b'+ss+r'\b'
found=set(re.compile(reg,re.IGNORECASE|re.UNICODE).findall(sfrom))
#if '(' in reg:# and len(reg)<10:
# print 'looking for ',reg, ' in ',sfrom
if found:
sfrom,ns=re.compile(reg,re.IGNORECASE|re.UNICODE).subn(' ',sfrom)
outlist+=(list(found))
return (sfrom.strip(),outlist)
def popStringIC(sfrom,ssearch):
""" Move any instances of ssearch from sfrom to outlist. A tuple
is returned: the possibly-modified sfrom and the outlist of removed
ssearch's. Case is ignored
Note: ssearch must be a *list* of strings or regexps.
"""
outlist=[]
for ss in [s.lower() for s in ssearch]:
# Replace/Match word, possibly at end of string
# Weirdness here: if the search-for string is short, let's make sure it's a word by itself:
if len(ss)<4:
reg=r'\b'+ss+r'\b'
else: # Otherwise, avoid \b because it doesn't think high unicode chars can be part of a word
reg=ss
sfrom,ns=re.compile(reg,re.IGNORECASE).subn(' ',sfrom)
#if "astra coup" in sfrom.lower() and "coup" in ss:
# 1/0
#print '-->',sfrom,ns
if ns:
#print '-----------------======================='
#print ' Sought "%s" in "%s" and found %d. Left with %s'%(fs,sfrom,ns,sfrom)
outlist.append(ss)
return (sfrom.strip(),outlist)
##############################################################################
##############################################################################
#
def uniqueInOrder(alist,key=None,drop=None): # Fastest order preserving
# 2016 update: use from more_itertools import unique_everseen ?
##########################################################################
##########################################################################
alist=list(alist) # Hmm. thi sis so that I can deal with mpl.array types? Aug2010
if not alist:
return(alist)
if isinstance(alist[0],list): # Then this is NOT the fastest!!
if all([not aa for aa in alist]):
return(alist[0:1])
def unique_items(L):
found = set()
for item in L:
if item[0] not in found:
yield item
found.add(item[0])
assert key==None
assert drop==None
return(list(unique_items(alist)))
# So it's a list of hashable items, hopefully.
if drop==None:
drop=[]
if key==None:
setu = {}
return [setu.setdefault(e,e) for e in alist if e not in setu and e not in drop]
else: # Find dicts with unique values of key:
NotDoneYet
# set = {}
#return [set.setdefault(e,e) for e in alist if e[key] not in [s[key] for s in set]]
##############################################################################
##############################################################################
#
def matchTableToTable(master,child,matchKeys=None,keepKeysMaster=None,keepKeysChild=None,masterKeyRow=0,childKeyRow=0,masterDataRow=1,childDataRow=1,primaryKey=None,reverse=False):
##########################################################################
##########################################################################
"""
See concordanceFinder.py, if it still exists standalone, for examples of *usage*. But the algorithm is here.
This has been a very useful tool for adding new fields to a concordance table, in particular for country-level data from different agencies which use different country names and sets.
primaryKey is a key in the master table. It is the thing we would like all others to relate to. e.g. wp5 when doing gallup data analysis. [No, I think this is obselete, according to below. You can ignore it.]
A "table" is a list of dicts. ie record order matters; column order does not.
You can pass either a dict or a filename for the first two arguments.
matchKeys is a list of pairs, each like [masterKey,childKey]. Each pair (only one is needed) specifies keys to match on. For instance, if the first one is ['iso3','ISO3'] then the child table rows will first be assigned to master table rows where the child row's ISO3 matches the master's iso3. Leftover rows of the child can then be matched based on the next key pair.
Lots still to deal with: what if multiple childs match to a master?
Or vice versa? which should I allow?...
Sept 2010: child can be a filename,
OCt 2010: needs more docuemntaiton thorughout. should report un=matched items!
"""
from copy import deepcopy
if reverse: # untested
child,master,matchKeys,keepKeysChild,keepKeysMaster,childKeyRow,masterKeyRow,childDataRow,masterDataRow,primaryKey=master,child,matchKeys,keepKeysMaster,keepKeysChild,masterKeyRow,childKeyRow,masterDataRow,childDataRow,primaryKey
matchKeys=[mk[1:2]+mk[:1]+mk[2:] for mk in matchKeys]
assert primaryKey==None # This is no longer used / implemented
# Check inputs
assert isinstance(matchKeys,list)
assert isinstance(matchKeys[0],list)
def table2tsv(filename,table):#,utf8=False):
fout=open( filename,'wt')
mk=sorted(table[0].keys())
fout.write(('\t'.join([k for k in mk])+'\n'+\
'\n'.join(['\t'.join([nm.get(k,'') for k in mk]) for nm in table])+'\n').encode('utf8'))
fout.close()
writeAutoFileMaster=False
writeAutoFileChild=False
if isinstance(master,basestring):
writeAutoFileMaster=True
masterFN=master
master=tsvToDict(master,keyRow=masterKeyRow,dataRow=masterDataRow,vectors=False)
if isinstance(master,dict): # hm, we need a list of dicts. This came as a dicto f vectors.
kk=master.keys()
master=[dict([[kkk,master[kkk][ii]] for kkk in kk]) for ii in range(len(master[kk[0]]))]
if isinstance(child,basestring):
writeAutoFileChild=True
childFN=child
child=tsvToDict(child,keyRow=childKeyRow,dataRow=childDataRow,vectors=False) # This becomes a list of dicts
if isinstance(child,dict): # hm, we need a list of dicts. This came as a dicto f vectors.
kk=child.keys()
child=[dict([[kkk,child[kkk][ii]] for kkk in kk]) for ii in range(len(child[kk[0]]))]
if not keepKeysMaster:
keepKeysMaster=master[0].keys()
if not keepKeysChild:
keepKeysChild=child[0].keys()
assert 'child' not in master
assert primaryKey not in child
def nopunclower(s):
return(''.join([ss for ss in s if ss not in ['. ,']]).lower())
master=deepcopy(master)
keysCopyToChild=list(set([kk[0] for kk in matchKeys]))
for c in child:
for k in keysCopyToChild:
c[k]=c.get(k,'')
newchild=deepcopy(child)
for mm in master: # Allow new data to be assigned onto master, duh.
mm['child']={}#dict([[k,''] for k in keepKeysChild])
for matchKeySet in matchKeys:
childKey,masterKey=matchKeySet[1],matchKeySet[0]
print 'Matching on %s (child) to %s (master)'%(childKey,masterKey)
print ' ',matchKeySet
# Find keys with which to order incoming data:
#childOrder=
for icc in range(len(child))[::-1]:
if child[icc][childKey]:
imatches=[imm for imm in range(len(master)) if nopunclower(child[icc][childKey])==nopunclower(master[imm][masterKey]) or (master[imm][masterKey] and len(matchKeySet)>2 and matchKeySet[2]=='partial' and ( nopunclower(child[icc][childKey]).find(nopunclower(master[imm][masterKey]))==0 or nopunclower(master[imm][masterKey]).find(nopunclower(child[icc][childKey]))==0 ) )]
if imatches:
if len(imatches)>1:
print child[icc][childKey]
print master[imm][masterKey]
imm=imatches[0]
print '%s looks like %s'%(child[icc][childKey],master[imm][masterKey]),
for copykey in keysCopyToChild:
if not child[icc][copykey]: #Overwrite unless not ''
child[icc][copykey]=master[imm][copykey]
newchild[icc]=deepcopy(child[icc])##master[imm]['child'])
master[imm]['child']=child.pop(icc)
print ': popped %d of child, len(%d)'%(icc,len(child)+1)
"""
from operator import itemgetter
sorted(child, key=itemgetter('text'))
"""
# Display matched:
#keyOrderM=keepKeysMaster#[kk for kk in master[0].keys() if not kk=='child']
#keyOrderC=keepKeysChild#child[0].keys()
""" Generate a list with all master's primary key (and match keys), with a single (??) match of children, followed by (append) list of unmatched children. ("newmaster"): """
newmaster=[]
for mm in master:
""" For new master, keep the primary key and any match keys: """
mrow=dict([[k,mm[k]] for k in list(set(keepKeysMaster+[mk[0] for mk in matchKeys]))])#[primaryKey]+[mk[0] for mk in matchKeys]
""" And also add the child keys: """
for ck in list(set(keepKeysChild+[mk[1] for mk in matchKeys])):###list(set(keepKeysChild+[])):
mrow[ck]=mm['child'].get(ck,'')
newmaster+=[mrow]
# And add the umatched children!!
for cc in child:
print ' FAILED to match this one with any key: '+' '.join(['%s="%s"'%(kk,str(cc[kk])) for kk in uniqueInOrder([mm[1] for mm in matchKeys])])
mrow=dict([[k,cc.get(k,'')] for k in keepKeysMaster])##[primaryKey]+[mk[0] for mk in matchKeys]])
for ck in list(set(keepKeysChild+[mk[1] for mk in matchKeys])):
mrow[ck]=cc[ck]
newmaster+=[mrow]
if child in [[]]:
print ' Match tables: managed to match every record!'
#print '\t'.join([mm[k] for k in keyOrderM]+ [mm['child'][k] for k in keyOrderC])#+'\n'
#for cc in child: # Unmatched remaining ones
# print '\t'.join([mm[k] for k in keyOrderM]+ [mm['child'][k] for k in keyOrderC])#+'\n'
""" Also generate a list of children simply with primary key added. ("newchild"): [done above] """
# I also want to add the unused master's to the newchild list:
for mm in master:
if not mm['child']:
newchild+=[mm]
if writeAutoFileChild:
# Now make a tsv to finish matching by hand:
table2tsv(childFN+'_automatch.tsv',newchild)
if writeAutoFileMaster:
# And make a tsv of child to check work:
table2tsv(masterFN+'_automatch.tsv',newmaster)
return(newmaster,newchild)
##############################################################################
##############################################################################
#
def importSpreadsheet(filename, masterKey=None):
##########################################################################
##########################################################################
#return(tsvToDict(filename,masterKey=None,multiOutputs=True)
"""
Grab a TSV spreadsheet and return both rows and columns.
First row is column headers.
If one column header is the master key, also offer a dict of rows based on that...
rows,cols,colDict,keyDictDict,keyDictList=importSpreadsheet(filename)
"""
rows=[LL.strip('\n').split('\t') for LL in open(filename,'rt').readlines()]
# Transpose a square list of lists:
cols=zip(*rows)
colDict={}
for col in cols:
if col[0] not in colDict:
colDict[col[0]]=col[1:]
keyDictDict={}
keyDictList={}
if masterKey:
for irow in range(len(colDict[masterKey])):
if colDict[masterKey][irow]:
keyDictDict[colDict[masterKey][irow]]=dict(zip(rows[0],rows[1+irow]))
keyDictList[colDict[masterKey][irow]]=rows[1+irow]
return(rows,cols,colDict,keyDictDict,keyDictList)
###########################################################################################
###
def orderListByRule(alist,orderRule,listKeys=None,dropIfKey=None):
###
#######################################################################################
""" Reorder alist according to the order specified in orderRule. The orderRule lists the order to be imposed on a set of keys. The keys are alist, if listkeys==None, or listkeys otherwise. That is, the length of listkeys must be the same as of alist. That is, listkeys are the tags on alist which determine the ordering. orderRule is a list of those same keys and maybe more which specifies the desired ordering.
There is an optional dropIfKey which lists keys of items that should be dropped outright.
A fix to deal with repeated entries in orderRule may mean that a better approach to this whole method is possible.
"""
# Remove duplicates in rule
def f7(seq):
seen = set()
seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))]
orderRule = f7(orderRule)
# Assign indices to do ordering
maxOR = len(orderRule)
orDict = dict(zip(orderRule, range(maxOR)))
alDict = dict(zip(range(maxOR, maxOR+len(alist)),
zip(alist if listKeys is None else listKeys, alist)))
outpairs = sorted( [[orDict.get(b[0],a),(b)] for a,b in alDict.items()] )
if dropIfKey is None: dropIfKey=[]
outL = [b[1] for a,b in outpairs if b[0] not in dropIfKey]
return outL
def test_orderListByRule():
L1 = [1,2,3,3,5]
L2 = [3,4,5,10]
assert orderListByRule(L1, L2) == [3, 3, 5, 1, 2]
assert orderListByRule(L1, L2, dropIfKey=[2,3]) == [5, 1,]
Lv = [c for c in 'abcce']
assert orderListByRule(Lv, L2, listKeys=L1) == ['c', 'c', 'e', 'a', 'b']
assert orderListByRule(Lv, L2, listKeys=L1, dropIfKey=[2,3]) == ['e','a']
# Test duplicates:
L1 = [1,2,3,3,5]
L2 = [3,4,5,10,3,4,8]
assert orderListByRule(L1, L2) == [3, 3, 5, 1, 2]
##########################################################################
##########################################################################
#
def transposedlist(lists):
#
##########################################################################
##########################################################################
if not lists: return []
return map(lambda *row: list(row), *lists)
##########################################################################
##########################################################################
#
def readTSV(filepath, header=False, columnDict=False):
#
##########################################################################
##########################################################################
"""
hmmm!? See ImportSpreadsheet, above! and tsvDict ... How many times have I reinvnted the weheel??
Depending on args, returns headers and data, or dict ,....
"""
ff=[aline.strip('\n').split('\t') for aline in open(filepath,'rt').readlines()]
if header or columnDict:
hh=ff[0]
ff=ff[1:]
else:
return(ff)
if header and not columnDict:
return(hh,ff)
if columnDict:
return(dict([[hh[ii],[fff[ii] for fff in ff]] for ii in range(len(hh))]))
if os.path.exists('/home/cpbl/gallup/inputData/macro/countrycode_main.tsv'):
masterCountryList=tsvToDict('/home/cpbl/gallup/inputData/macro/countrycode_main.tsv',dataRow=4,keyRow=3)
import pandas as pd
dfCountryList=pd.DataFrame(masterCountryList)
##########################################################################
##########################################################################
#
def getCountryIDfromName(names):
#
##########################################################################
##########################################################################
""" Right now, returns universal country ID for a given name string.
Still need to allow multiple guesses for the name of each country as param, somewhow
2010 Sept: HUH? why not country_bestName??
"""
if not os.path.exists('/home/cpbl/gallup/inputData/macro/countrycode_main.tsv'):
NOT_AVAILABLE
return()
if isinstance(names,list):
return([getCountryIDfromName(nn) for nn in names])
assert isinstance(names,basestring)
#if not masterCountryList:
master=masterCountryList#tsvToDict('/home/cpbl/gallup/inputData/macro/countrycode_main.tsv',dataRow=4,keyRow=3)
favNames=[
['country_kauffman2'],
['country_UN'],
['country_WHO'],
['country_GWP3_wp5'],
['country_UN','partial'],
]
mtab={}
for kk in favNames:
mtab=dict([[mm[kk[0]],mm['countryCode_CPBL']] for mm in master])
if names in mtab:
return(mtab[names])
""" Should here check for partial!! """
return('')
##########################################################################
##########################################################################
#
def getDictCountryIDtoISO3():
#
##########################################################################
##########################################################################
""" Returns a dict translating my country code to ISO3 name
N.B. This is NOT wp5 (Gallup). Nor is it returning the long name. For those, see recodeGallup.py [Sep 2010]
Well, actually, this will now issue a warning if it conflicts with the latest wp5.
"""
master=masterCountryList#tsvToDict('/home/cpbl/gallup/inputData/macro/countrycode_main.tsv',dataRow=4,keyRow=3)
master=tsvToDict('/home/cpbl/gallup/inputData/macro/countrycode_main.tsv',dataRow=4,keyRow=3)
print [mm for mm in master if mm['countryCode_CPBL']=='167']
for mmm in master:
assert mmm['countryCode_CPBL']==mmm['countryCode_GWP3_wp5'] or not mmm['countryCode_GWP3_wp5'] # Ensure no lost wp5s
response=dict([[mm['countryCode_CPBL'],mm.get('countryCode_ISO3','') ] for mm in master if mm['countryCode_CPBL']] )
return(response)
def flattenList(listoflists,unique=False):#,noCopy=False):):
"""
what about these funny methods!?
[item for sublist in l for item in sublist] # YES!! This is brilliant for one-level reduction. I've no idea how it works.
or sum(thelist,[]) !!!
CAUTION: The method below will FAIL on a list of lists of dicts. It will treat dicts like a list, and return its keys!
"""
from pylab import flatten
if unique:
return(uniqueInOrder([xx for xx in flatten(listoflists)]))
else:
return([xx for xx in flatten(listoflists)])
def str2pathname(ss, includes_path = False, check=False):
""" Remove some characters from a string, for safety (or simplicity) on POSIX systems..
If passed with "check=True", it will simply check whether the string needs fixing.
If passed with "includes_path", it will allow forward slashes but otherwise behave the same.
"""
if check:
return not ss == str2pathname(ss, includes_path = includes_path, check=False)
subs=[
['_','-'],
[r'$>$','gt'],
]
todrop = u"""?"':.,()’ """+(not includes_path)*'/' #'
for asub in subs:
ss = ss.replace(asub[0],asub[1])
ss=''.join([sss for sss in ss if sss not in todrop])
return(ss)
def fileOlderThan(afile,bfiles,ageWarning=False):
""" ie says whether the first fiel, afile, needs updating based on its parent, bfile. ie iff afile does not exist or is older than bfile, this function returns true.
bfile can also be a list of files. in this case, the function returns true if any of the bfiles is younger than the target file, afile; ie afile needs to be updated based on its antecedents, bfiles.
afile can also be a list of files... then the function returns true if any of the bfiles is younger than any of the afiles.
Rewritten, sept 2010, but features not yet complete. now afile,bfiles can be a filename, a list of filenames, or an mtime. And it's not vastly less inefficient than the first, recursive algorithm.
"""
def oldestmtime(listoffiles): # Return oldest mtime of a list of filenames
if any([not os.path.exists(afile) for afile in listoffiles]):
return(-999999)
return(min([os.path.getmtime(afile) for afile in listoffiles]))
def newestmtime(listoffiles): # Return newst mtime of a list of filenames
missingF=[afile for afile in listoffiles if not os.path.exists(afile)]
if missingF:
print('File assumed to exist is missing!: ',missingF)
assert not missingF # assert all([os.path.exists(afile) for afile in listoffiles])
return(max([os.path.getmtime(afile) for afile in listoffiles]))
# Ensure are lists
if isinstance(afile,basestring):
afile=[afile]
if isinstance(bfiles,basestring):
bfiles=[bfiles]
# Compare ages:
aa=afile
bb=bfiles
if isinstance(afile,basestring) or isinstance(afile,list):
aa=oldestmtime(afile)
if isinstance(bfiles,basestring) or isinstance(bfiles,list):
bb=newestmtime(bfiles)
# So now I hope aa and bb contain the mtimes, or -999999
isOlder= aa<bb or aa<-99999
#if isinstance(afile,list):
# return(any([fileOlderThan(af,bfiles,ageWarning=ageWarning) for af in afile]))
#if isinstance(bfiles,list):
# return(any([fileOlderThan(afile,bf,ageWarning=ageWarning) for bf in bfiles]))
# Check for existence of bfiles:
gotReq=False
def cpblRequireDummy(afile):
return
try:
from cpblMake import cpblRequire # I could skip this if it doesn't exist, or....
gotReq=True
except:
cpblRequire=cpblRequireDummy
#print(' Failed to import cpblMake...')
#pass
for bf in bfiles:
if gotReq:
cpblRequire(bf)
# Check for exists but getting aged:
if 1:#####isOlder: # Don't bother with warnings if we know it's older (worse than aged)
for af in afile:
if os.path.exists(af) and os.path.getmtime(af) < time.time()-30*24*3600 and ageWarning:
print """ (N.B.: %s is older than a month. Consider recreating it?)"""%af
return( isOlder)# not os.path.exists(afile) or os.path.getmtime(afile)<os.path.getmtime(bfiles) )
def fileOlderThanAMonth(af):
import time
return((not os.path.exists(af)) or (os.path.getmtime(af) < time.time()-30*24*3600))
def wassert(assertthat,msg):
if not assertthat:
cwarning(msg)
return()
def cwarning(msg):
print '\n\n'+msg+'\n\n'
raw_input('Confirm to continue: ')
print '\n\n'
return()