Summing a Python Dictionary that Has Two Nested Keys Per Value

Viewed 57

My Python script is designed to count the number of areas at or above a prescribed threshold using SciKit Image. It also computes the areas of these regions, and their average intensity. However, it is designed in a way such that the region coordinate is dependent on the hour coordinate, if that makes sense. In other words, there are a different number of regions for each hour. This makes the output difficult to work with, but I have been attempting to implement dictionaries to solve this:

# Set up dictionaries
regions_by_hours = {}
contour_counts = {}
contour_areas = {}
contour_avg_vals = {}

threshold = 10.7

# For loop to loop through times in dataset for LOW LEVEL VERTICAL VELOCITY
for hour in range(1, 17, 1):
    
    # Get variables
    vertVelo = cm1['w']
    zf = cm1['zf']
    
    # Slice vertical velocity for 1-4km AGL
    vertVeloSlice = vertVelo[hour, 11:20, :, :]
    
    # Create composite image of vertical velocity
    vertVeloComp = vertVeloSlice.max(dim='zf')
    
    # Set threshold for vertical velocity
    threshVertVelo = vertVeloComp > threshold
    
    # Create buffered vertical velocity
    res = '1km'
    buffer = '5km'
    buffer_px = int((units(buffer) / units(res)).m)
    bufferedVV = skimage.morphology.binary_dilation(threshVertVelo.values, footprint=skimage.morphology.disk(buffer_px))
    
    # Create initial labeled image and remove small objects
    initLabeledImage, _ = scipy.ndimage.label(bufferedVV, np.ones((3,3), dtype=int))
    skimage.morphology.remove_small_objects(initLabeledImage, min_size=buffer_px * 3, connectivity=2)
    
    # Create list for vertical velocity subsets
    subsets_VV = []
    
    # For loop to fill subsets_VV
    for region in skimage.measure.regionprops(initLabeledImage):
        subsets_VV.append(vertVeloComp.isel(yh=slice(region.bbox[0], region.bbox[2]),
                                            xh=slice(region.bbox[1], region.bbox[3])))
        #plt.imshow(initLabeledImage)
    
    # Loop through subsets_VV and calculate properties
    regions_by_hours[hour] = tuple(range(len(subsets_VV)))
    for subset_idx, subset_VV in enumerate(subsets_VV):
        labeled_image, num_contours = skimage.measure.label(subset_VV.values >= threshold, return_num=True)
        contour_regions = skimage.measure.regionprops(labeled_image, intensity_image=subset_VV.values)
        contour_counts[hour, subset_idx] = num_contours
        contour_areas[hour, subset_idx] = sum(region.area for region in contour_regions)
        contour_avg_vals[hour, subset_idx] = (region.intensity_mean for region in contour_regions)

The following is the output with the first number in parenthesis being the hour "coordinate" and the second number being the region "coordinate":

 {(1, 0): 36,
 (2, 0): 16,
 (3, 0): 16,
 (4, 0): 20,
 (5, 0): 1,
 (5, 1): 1,
 (5, 2): 6,
 (5, 3): 6,
 (5, 4): 6,
 (5, 5): 1,
 (5, 6): 1,
 (5, 7): 1,
 (5, 8): 1,
 (5, 9): 6,
 (5, 10): 1,
 (5, 11): 1,
 (6, 0): 26,
 (6, 1): 26,
 (6, 2): 26,
 (6, 3): 26,
 (7, 0): 2,
 (7, 1): 2,
 (7, 2): 26,
 (7, 3): 3,
 (7, 4): 3,
 (7, 5): 26,
 (7, 6): 26,
 (7, 7): 3,
 (7, 8): 3,
 (7, 9): 26,
 (7, 10): 2,
 (7, 11): 2,
 (8, 0): 6,
 (8, 1): 48,
 (8, 2): 48,
 (8, 3): 48,
 (8, 4): 6,
 (8, 5): 6,
 (8, 6): 48,
 (8, 7): 6,
 (9, 0): 252,
 (9, 1): 8,
 (10, 0): 2,
 (10, 1): 2,
 (10, 2): 1,
 (10, 3): 1,
 (10, 4): 352,
 (10, 5): 2,
 (10, 6): 2,
 (10, 7): 2,
 (10, 8): 2,
 (10, 9): 1,
 (10, 10): 1,
 (10, 11): 2,
 (10, 12): 2,
 (11, 0): 2,
 (11, 1): 2,
 (11, 2): 2,
 (11, 3): 21,
 (11, 4): 21,
 (11, 5): 98,
 (11, 6): 98,
 (11, 7): 98,
 (11, 8): 2,
 (11, 9): 2,
 (11, 10): 2,
 (11, 11): 2,
 (11, 12): 2,
 (11, 13): 2,
 (11, 14): 21,
 (11, 15): 21,
 (11, 16): 98,
 (11, 17): 2,
 (11, 18): 2,
 (11, 19): 2,
 (12, 0): 2,
 (12, 1): 712,
 (12, 2): 4,
 (12, 3): 4,
 (12, 4): 2,
 (12, 5): 2,
 (12, 6): 4,
 (12, 7): 4,
 (12, 8): 2,
 (13, 0): 118,
 (13, 1): 111,
 (13, 2): 111,
 (13, 3): 118,
 (13, 4): 118,
 (13, 5): 111,
 (13, 6): 111,
 (13, 7): 118,
 (14, 0): 752,
 (14, 1): 38,
 (14, 2): 38,
 (14, 3): 38,
 (14, 4): 38,
 (15, 0): 144,
 (15, 1): 2,
 (15, 2): 2,
 (15, 3): 61,
 (15, 4): 61,
 (15, 5): 1,
 (15, 6): 1,
 (15, 7): 1,
 (15, 8): 1,
 (15, 9): 12,
 (15, 10): 12,
 (15, 11): 7,
 (15, 12): 7,
 (15, 13): 1,
 (15, 14): 1,
 (15, 15): 1,
 (15, 16): 1,
 (15, 17): 144,
 (15, 18): 144,
 (15, 19): 2,
 (15, 20): 2,
 (15, 21): 2,
 (15, 22): 2,
 (15, 23): 1,
 (15, 24): 1,
 (15, 25): 1,
 (15, 26): 1,
 (15, 27): 7,
 (15, 28): 7,
 (15, 29): 61,
 (15, 30): 61,
 (15, 31): 12,
 (15, 32): 144,
 (15, 33): 12,
 (15, 34): 1,
 (15, 35): 1,
 (15, 36): 1,
 (15, 37): 1,
 (15, 38): 2,
 (15, 39): 2,
 (16, 0): 1096,
 (16, 1): 3,
 (16, 2): 3,
 (16, 3): 6,
 (16, 4): 6,
 (16, 5): 3,
 (16, 6): 3,
 (16, 7): 6,
 (16, 8): 6,
 (16, 9): 1,
 (16, 10): 1,
 (16, 11): 1,
 (16, 12): 1,
 (16, 13): 6,
 (16, 14): 6,
 (16, 15): 6,
 (16, 16): 6,
 (16, 17): 3,
 (16, 18): 3,
 (16, 19): 3,
 (16, 20): 3}

What I would like to do is be able to combine all the region coordinates into one for a given hour. So instead of there being (5, 0) through (5, 11), it would simply be an entry for hour 5. How would I go about doing this with dictionaries, or is it even possible?

Thank you for the assistance!

2 Answers

We can do this using itertools groupby and some dictionary comprehension~

Doing:

from itertools import groupby
out = {k:[x[1] for x in v] for k, v in groupby(data.items(), key=lambda x: x[0][0])}
print(out)

Output:

{1: [36],
 2: [16],
 3: [16],
 4: [20],
 5: [1, 1, 6, 6, 6, 1, 1, 1, 1, 6, 1, 1],
 6: [26, 26, 26, 26],
 7: [2, 2, 26, 3, 3, 26, 26, 3, 3, 26, 2, 2],
 8: [6, 48, 48, 48, 6, 6, 48, 6],
 9: [252, 8],
 10: [2, 2, 1, 1, 352, 2, 2, 2, 2, 1, 1, 2, 2],
 11: [2, 2, 2, 21, 21, 98, 98, 98, 2, 2, 2, 2, 2, 2, 21, 21, 98, 2, 2, 2],
 12: [2, 712, 4, 4, 2, 2, 4, 4, 2],
 13: [118, 111, 111, 118, 118, 111, 111, 118],
 14: [752, 38, 38, 38, 38],
 15: [144, 2, 2, 61, 61, 1, 1, 1, 1, 12, 12, 7, 7, 1, 1, 1, 1, 144, 144, 2, 2, 2, 2, 1, 1, 1, 1, 7, 7, 61, 61, 12, 144, 12, 1, 1, 1, 1, 2, 2],
 16: [1096, 3, 3, 6, 6, 3, 3, 6, 6, 1, 1, 1, 1, 6, 6, 6, 6, 3, 3, 3, 3]}

Breakdown:

# This gives a list of 2-item tuples.
>>> print(list(groupby(data.items(), key=lambda x: x[0][0])))
[(1, <itertools._grouper object at 0x000001FACF352B00>), 
 (2, <itertools._grouper object at 0x000001FACF3502B0>),
 (3, <itertools._grouper object at 0x000001FACF351FC0>),
 (4, <itertools._grouper object at 0x000001FACF353F70>),
 (5, <itertools._grouper object at 0x000001FACF352470>)]

# for k, v in
    # Refers to k == 1 and v == <itertools._grouper object> ... etc.

# Each v looks like (9's shown for a short example):
[((9, 0), 252), ((9, 1), 8)]

# So, [x[1] for x in v] is extracting each value from these named tuples.

This could also be written as:

new_data = {}
for (k, _), v in data.items():
    new_data.setdefault(k, [])
    new_data[k].append(v)

print(new_data)

Without importing any library:

coord = {(1, 0): 1,
(2, 0): 4,
(3, 0): 8,
(4, 0): 20,
(5, 0): 1,
(5, 1): 1,
(5, 2): 5,
(5, 3): 5,
(5, 4): 5,
(5, 5): 1,
(5, 6): 1,
(5, 7): 1,
(5, 8): 1,
(5, 9): 5,
(5, 10): 1,
(5, 11): 1}
 
res = {}
for (my_key, _), my_value in coord.items():
    lst = res.get(my_key, []) # this will return an empty list in my_key not in res dictionary
    lst.append(my_value)
    res[my_key] = lst

print(res)

OUTPUT

{1: [1], 2: [4], 3: [8], 4: [20], 5: [1, 1, 5, 5, 5, 1, 1, 1, 1, 5, 1, 1]}
Related